Skip to main content

src/packkit@bzip2.erl

-module(packkit@bzip2).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/packkit/bzip2.gleam").
-export([codec/0, decode_with_limits/2, decode/1, encode_with_level/2, encode/1]).
-export_type([huffman_table/0, build_acc/0, huffman_stream_step/0, reader/0, huff_node/0, writer/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(
    " bzip2 codec — pure Gleam decoder for `.bz2` streams.\n"
    "\n"
    " The decoder consumes the `\"BZh\"` magic, a single-character\n"
    " block-size label (1..9), one or more compressed blocks, and the\n"
    " stream end-marker plus 32-bit combined CRC.  Each block is\n"
    " reversed through Huffman decoding, MTF inversion, inverse\n"
    " Burrows-Wheeler, and the RLE1 expansion that bzip2 applies to the\n"
    " raw input before transformation.  The encoder is intentionally\n"
    " deferred.\n"
).

-type huffman_table() :: {huffman_table,
        integer(),
        integer(),
        gleam@dict:dict(integer(), integer()),
        gleam@dict:dict(integer(), integer()),
        gleam@dict:dict(integer(), integer())}.

-type build_acc() :: {build_acc,
        gleam@dict:dict(integer(), integer()),
        gleam@dict:dict(integer(), integer()),
        gleam@dict:dict(integer(), integer()),
        integer(),
        integer(),
        integer()}.

-type huffman_stream_step() :: {huffman_stream_done,
        list(integer()),
        integer(),
        reader()} |
    {huffman_stream_continue,
        reader(),
        list(integer()),
        integer(),
        integer(),
        integer(),
        list(integer()),
        integer()}.

-type reader() :: {reader, integer(), integer(), bitstring(), boolean()}.

-type huff_node() :: {huff_leaf, integer(), integer()} |
    {huff_internal, integer(), huff_node(), huff_node()}.

-type writer() :: {writer, list(integer()), integer(), integer()}.

-file("src/packkit/bzip2.gleam", 38).
?DOC(" bzip2 codec smart constructor.\n").
-spec codec() -> packkit@codec:codec().
codec() ->
    packkit@codec:bzip2().

-file("src/packkit/bzip2.gleam", 145).
-spec parse_stream_header(bitstring()) -> {ok, bitstring()} |
    {error, packkit@error:codec_error()}.
parse_stream_header(Bytes) ->
    case Bytes of
        <<16#42, 16#5A, 16#68, Level, Rest/binary>> when (Level >= 16#31) andalso (Level =< 16#39) ->
            {ok, Rest};

        _ ->
            {error,
                {codec_invalid_data, <<"invalid bzip2 stream header"/utf8>>}}
    end.

-file("src/packkit/bzip2.gleam", 196).
?DOC(
    " Recover the byte-aligned tail of the bit reader so the caller can\n"
    " look for another stream's `\"BZh\"` magic.  bzip2 streams pad to\n"
    " the next byte boundary after the stream CRC, so any partial bits\n"
    " still sitting in `reader.buffer` are padding and we discard them.\n"
).
-spec byte_aligned_remainder(reader()) -> bitstring().
byte_aligned_remainder(Reader) ->
    _ = erlang:element(3, Reader),
    erlang:element(4, Reader).

-file("src/packkit/bzip2.gleam", 201).
-spec combine_block_crc(integer(), integer()) -> integer().
combine_block_crc(Combined, Block_crc) ->
    Rotated = erlang:'bor'(
        erlang:'band'(erlang:'bsl'(Combined, 1), 16#FFFFFFFF),
        erlang:'bsr'(Combined, 31)
    ),
    erlang:'band'(erlang:'bxor'(Rotated, Block_crc), 16#FFFFFFFF).

-file("src/packkit/bzip2.gleam", 210).
-spec verify_block_crc(bitstring(), integer()) -> {ok, nil} |
    {error, packkit@error:codec_error()}.
verify_block_crc(Data, Expected) ->
    case packkit@checksum:bzip2_crc32(Data) =:= Expected of
        true ->
            {ok, nil};

        false ->
            {error, {codec_invalid_data, <<"bzip2 block CRC mismatch"/utf8>>}}
    end.

-file("src/packkit/bzip2.gleam", 220).
-spec append_with_limit(bitstring(), bitstring(), packkit@limit:limits()) -> {ok,
        bitstring()} |
    {error, packkit@error:codec_error()}.
append_with_limit(Output, Chunk, Limits) ->
    Projected = erlang:byte_size(Output) + erlang:byte_size(Chunk),
    case Projected > packkit@limit:max_output_bytes(Limits) of
        true ->
            {error,
                {codec_limit_exceeded, <<"max_output_bytes"/utf8>>, Projected}};

        false ->
            {ok, gleam_stdlib:bit_array_concat([Output, Chunk])}
    end.

-file("src/packkit/bzip2.gleam", 350).
-spec collect_group_bytes(integer(), integer(), integer(), list(integer())) -> list(integer()).
collect_group_bytes(Bits, Group, Offset, Acc) ->
    case Offset >= 16 of
        true ->
            Acc;

        false ->
            Mask = erlang:'bsl'(1, 15 - Offset),
            Acc@1 = case erlang:'band'(Bits, Mask) of
                0 ->
                    Acc;

                _ ->
                    [(Group * 16) + Offset | Acc]
            end,
            collect_group_bytes(Bits, Group, Offset + 1, Acc@1)
    end.

-file("src/packkit/bzip2.gleam", 380).
-spec init_selector_stack(integer(), integer(), list(integer())) -> list(integer()).
init_selector_stack(Num, Index, Acc) ->
    case Index >= Num of
        true ->
            lists:reverse(Acc);

        false ->
            init_selector_stack(Num, Index + 1, [Index | Acc])
    end.

-file("src/packkit/bzip2.gleam", 421).
-spec pick_at(list(integer()), integer(), list(integer())) -> {integer(),
    list(integer())}.
pick_at(Stack, Index, Prefix) ->
    case {Stack, Index} of
        {[Head | Rest], 0} ->
            {Head, lists:append(lists:reverse(Prefix), Rest)};

        {[Head@1 | Rest@1], _} ->
            pick_at(Rest@1, Index - 1, [Head@1 | Prefix]);

        {[], _} ->
            {0, lists:reverse(Prefix)}
    end.

-file("src/packkit/bzip2.gleam", 557).
-spec canonical_loop(list({integer(), integer()}), build_acc()) -> build_acc().
canonical_loop(Sorted, Acc) ->
    case Sorted of
        [] ->
            Acc;

        [{Sym, Length} | Rest] ->
            {Code, Base, Limit} = case Length =:= erlang:element(7, Acc) of
                true ->
                    {erlang:element(6, Acc),
                        erlang:element(2, Acc),
                        erlang:element(3, Acc)};

                false ->
                    case erlang:element(7, Acc) of
                        0 ->
                            {0,
                                gleam@dict:insert(
                                    erlang:element(2, Acc),
                                    Length,
                                    erlang:element(5, Acc)
                                ),
                                erlang:element(3, Acc)};

                        _ ->
                            Shift = Length - erlang:element(7, Acc),
                            Prev_limit = gleam@dict:insert(
                                erlang:element(3, Acc),
                                erlang:element(7, Acc),
                                erlang:element(6, Acc) - 1
                            ),
                            New_code = erlang:'bsl'(
                                erlang:element(6, Acc),
                                Shift
                            ),
                            {New_code,
                                gleam@dict:insert(
                                    erlang:element(2, Acc),
                                    Length,
                                    erlang:element(5, Acc) - New_code
                                ),
                                Prev_limit}
                    end
            end,
            Symbols = gleam@dict:insert(
                erlang:element(4, Acc),
                erlang:element(5, Acc),
                Sym
            ),
            canonical_loop(
                Rest,
                {build_acc,
                    Base,
                    Limit,
                    Symbols,
                    erlang:element(5, Acc) + 1,
                    Code + 1,
                    Length}
            )
    end.

-file("src/packkit/bzip2.gleam", 599).
-spec enumerate_lengths(
    list(integer()),
    integer(),
    list({integer(), integer()})
) -> list({integer(), integer()}).
enumerate_lengths(Lengths, Index, Acc) ->
    case Lengths of
        [] ->
            lists:reverse(Acc);

        [Head | Rest] ->
            enumerate_lengths(Rest, Index + 1, [{Index, Head} | Acc])
    end.

-file("src/packkit/bzip2.gleam", 611).
-spec find_min_length(list(integer()), integer()) -> integer().
find_min_length(Lengths, Best) ->
    case Lengths of
        [] ->
            case Best of
                21 ->
                    1;

                _ ->
                    Best
            end;

        [Head | Rest] ->
            Best@1 = case Head < Best of
                true ->
                    Head;

                false ->
                    Best
            end,
            find_min_length(Rest, Best@1)
    end.

-file("src/packkit/bzip2.gleam", 628).
-spec find_max_length(list(integer()), integer()) -> integer().
find_max_length(Lengths, Best) ->
    case Lengths of
        [] ->
            Best;

        [Head | Rest] ->
            Best@1 = case Head > Best of
                true ->
                    Head;

                false ->
                    Best
            end,
            find_max_length(Rest, Best@1)
    end.

-file("src/packkit/bzip2.gleam", 661).
-spec prepend_reversed(
    list({integer(), integer()}),
    list({integer(), integer()})
) -> list({integer(), integer()}).
prepend_reversed(Values, Acc) ->
    case Values of
        [] ->
            Acc;

        [Head | Rest] ->
            prepend_reversed(Rest, [Head | Acc])
    end.

-file("src/packkit/bzip2.gleam", 641).
-spec sort_pairs_by_length(
    list({integer(), integer()}),
    integer(),
    integer(),
    list({integer(), integer()})
) -> list({integer(), integer()}).
sort_pairs_by_length(Pairs, Length, Max_length, Acc) ->
    case Length > Max_length of
        true ->
            lists:reverse(Acc);

        false ->
            Bucket = gleam@list:filter(
                Pairs,
                fun(Pair) -> erlang:element(2, Pair) =:= Length end
            ),
            sort_pairs_by_length(
                Pairs,
                Length + 1,
                Max_length,
                prepend_reversed(Bucket, Acc)
            )
    end.

-file("src/packkit/bzip2.gleam", 673).
-spec list_to_dict(list(GXM), integer(), gleam@dict:dict(integer(), GXM)) -> gleam@dict:dict(integer(), GXM).
list_to_dict(Values, Index, Acc) ->
    case Values of
        [] ->
            Acc;

        [Head | Rest] ->
            list_to_dict(Rest, Index + 1, gleam@dict:insert(Acc, Index, Head))
    end.

-file("src/packkit/bzip2.gleam", 885).
-spec emit_byte(list(integer()), integer(), integer(), packkit@limit:limits()) -> {ok,
        {list(integer()), integer()}} |
    {error, packkit@error:codec_error()}.
emit_byte(Out_rev, Out_len, Byte, Limits) ->
    gleam@bool:guard(
        (Out_len + 1) > packkit@limit:max_output_bytes(Limits),
        {error,
            {codec_limit_exceeded, <<"max_output_bytes"/utf8>>, Out_len + 1}},
        fun() -> {ok, {[Byte | Out_rev], Out_len + 1}} end
    ).

-file("src/packkit/bzip2.gleam", 901).
-spec repeat_prepend(integer(), integer(), list(integer())) -> list(integer()).
repeat_prepend(Value, Count, Acc) ->
    case Count of
        0 ->
            Acc;

        _ ->
            repeat_prepend(Value, Count - 1, [Value | Acc])
    end.

-file("src/packkit/bzip2.gleam", 858).
-spec flush_run(
    list(integer()),
    integer(),
    list(integer()),
    integer(),
    packkit@limit:limits()
) -> {ok, {list(integer()), integer()}} | {error, packkit@error:codec_error()}.
flush_run(Out_rev, Out_len, Mtf, Pending, Limits) ->
    case Pending of
        0 ->
            {ok, {Out_rev, Out_len}};

        _ ->
            Front = case Mtf of
                [Head | _] ->
                    Head;

                [] ->
                    0
            end,
            gleam@bool:guard(
                (Out_len + Pending) > packkit@limit:max_output_bytes(Limits),
                {error,
                    {codec_limit_exceeded,
                        <<"max_output_bytes"/utf8>>,
                        Out_len + Pending}},
                fun() ->
                    Out_rev@1 = repeat_prepend(Front, Pending, Out_rev),
                    {ok, {Out_rev@1, Out_len + Pending}}
                end
            )
    end.

-file("src/packkit/bzip2.gleam", 920).
-spec mtf_pick_loop(list(integer()), integer(), list(integer())) -> {ok,
        {integer(), list(integer()), list(integer())}} |
    {error, nil}.
mtf_pick_loop(Mtf, Index, Prefix) ->
    case {Mtf, Index} of
        {[Head | Rest], 0} ->
            {ok, {Head, Prefix, Rest}};

        {[Head@1 | Rest@1], _} ->
            mtf_pick_loop(Rest@1, Index - 1, [Head@1 | Prefix]);

        {[], _} ->
            {error, nil}
    end.

-file("src/packkit/bzip2.gleam", 908).
-spec mtf_pick_byte(list(integer()), integer()) -> {ok,
        {integer(), list(integer())}} |
    {error, packkit@error:codec_error()}.
mtf_pick_byte(Mtf, Index) ->
    case mtf_pick_loop(Mtf, Index, []) of
        {ok, {Byte, Prefix, Rest}} ->
            {ok, {Byte, [Byte | lists:append(lists:reverse(Prefix), Rest)]}};

        {error, _} ->
            {error,
                {codec_invalid_data, <<"bzip2 MTF index out of range"/utf8>>}}
    end.

-file("src/packkit/bzip2.gleam", 932).
-spec list_to_indexed_dict(
    list(integer()),
    integer(),
    gleam@dict:dict(integer(), integer())
) -> gleam@dict:dict(integer(), integer()).
list_to_indexed_dict(Values, Index, Acc) ->
    case Values of
        [] ->
            Acc;

        [Head | Rest] ->
            list_to_indexed_dict(
                Rest,
                Index + 1,
                gleam@dict:insert(Acc, Index, Head)
            )
    end.

-file("src/packkit/bzip2.gleam", 1012).
-spec count_bytes(
    gleam@dict:dict(integer(), integer()),
    integer(),
    integer(),
    gleam@dict:dict(integer(), integer())
) -> gleam@dict:dict(integer(), integer()).
count_bytes(L_string, Index, Length, Acc) ->
    case Index >= Length of
        true ->
            Acc;

        false ->
            Byte = case gleam_stdlib:map_get(L_string, Index) of
                {ok, V} ->
                    V;

                {error, _} ->
                    0
            end,
            Current = case gleam_stdlib:map_get(Acc, Byte) of
                {ok, V@1} ->
                    V@1;

                {error, _} ->
                    0
            end,
            count_bytes(
                L_string,
                Index + 1,
                Length,
                gleam@dict:insert(Acc, Byte, Current + 1)
            )
    end.

-file("src/packkit/bzip2.gleam", 1039).
-spec build_cumulative(
    gleam@dict:dict(integer(), integer()),
    integer(),
    integer(),
    gleam@dict:dict(integer(), integer())
) -> gleam@dict:dict(integer(), integer()).
build_cumulative(Counts, Symbol, Acc, Out) ->
    case Symbol >= 256 of
        true ->
            Out;

        false ->
            Out@1 = gleam@dict:insert(Out, Symbol, Acc),
            N = case gleam_stdlib:map_get(Counts, Symbol) of
                {ok, V} ->
                    V;

                {error, _} ->
                    0
            end,
            build_cumulative(Counts, Symbol + 1, Acc + N, Out@1)
    end.

-file("src/packkit/bzip2.gleam", 1058).
-spec build_t(
    gleam@dict:dict(integer(), integer()),
    integer(),
    integer(),
    gleam@dict:dict(integer(), integer()),
    gleam@dict:dict(integer(), integer())
) -> gleam@dict:dict(integer(), integer()).
build_t(L_string, Index, Length, Cumulative, T) ->
    case Index >= Length of
        true ->
            T;

        false ->
            Byte = case gleam_stdlib:map_get(L_string, Index) of
                {ok, V} ->
                    V;

                {error, _} ->
                    0
            end,
            Slot = case gleam_stdlib:map_get(Cumulative, Byte) of
                {ok, V@1} ->
                    V@1;

                {error, _} ->
                    0
            end,
            Cumulative@1 = gleam@dict:insert(Cumulative, Byte, Slot + 1),
            build_t(
                L_string,
                Index + 1,
                Length,
                Cumulative@1,
                gleam@dict:insert(T, Slot, Index)
            )
    end.

-file("src/packkit/bzip2.gleam", 1088).
-spec walk_bwt(
    gleam@dict:dict(integer(), integer()),
    gleam@dict:dict(integer(), integer()),
    integer(),
    integer(),
    integer(),
    gleam@dict:dict(integer(), integer())
) -> gleam@dict:dict(integer(), integer()).
walk_bwt(L_string, T, Pos, Index, Length, Out) ->
    case Index >= Length of
        true ->
            Out;

        false ->
            Next = case gleam_stdlib:map_get(T, Pos) of
                {ok, V} ->
                    V;

                {error, _} ->
                    0
            end,
            Byte = case gleam_stdlib:map_get(L_string, Next) of
                {ok, V@1} ->
                    V@1;

                {error, _} ->
                    0
            end,
            walk_bwt(
                L_string,
                T,
                Next,
                Index + 1,
                Length,
                gleam@dict:insert(Out, Index, Byte)
            )
    end.

-file("src/packkit/bzip2.gleam", 1001).
-spec inverse_bwt(gleam@dict:dict(integer(), integer()), integer(), integer()) -> gleam@dict:dict(integer(), integer()).
inverse_bwt(L_string, Length, Orig_ptr) ->
    Counts = count_bytes(L_string, 0, Length, maps:new()),
    Cumulative = build_cumulative(Counts, 0, 0, maps:new()),
    T = build_t(L_string, 0, Length, Cumulative, maps:new()),
    walk_bwt(L_string, T, Orig_ptr, 0, Length, maps:new()).

-file("src/packkit/bzip2.gleam", 1127).
-spec rle1_decode_loop(
    gleam@dict:dict(integer(), integer()),
    integer(),
    integer(),
    integer(),
    integer(),
    list(integer())
) -> list(integer()).
rle1_decode_loop(Input, Index, Length, Last_byte, Run, Acc) ->
    case Index >= Length of
        true ->
            Acc;

        false ->
            Byte = case gleam_stdlib:map_get(Input, Index) of
                {ok, V} ->
                    V;

                {error, _} ->
                    0
            end,
            case Run =:= 4 of
                true ->
                    Acc@1 = repeat_prepend(Last_byte, Byte, Acc),
                    rle1_decode_loop(Input, Index + 1, Length, -1, 0, Acc@1);

                false ->
                    case Byte =:= Last_byte of
                        true ->
                            rle1_decode_loop(
                                Input,
                                Index + 1,
                                Length,
                                Byte,
                                Run + 1,
                                [Byte | Acc]
                            );

                        false ->
                            rle1_decode_loop(
                                Input,
                                Index + 1,
                                Length,
                                Byte,
                                1,
                                [Byte | Acc]
                            )
                    end
            end
    end.

-file("src/packkit/bzip2.gleam", 1163).
-spec bytes_list_to_bit_array(list(integer()), bitstring()) -> bitstring().
bytes_list_to_bit_array(Bytes, Acc) ->
    case Bytes of
        [] ->
            Acc;

        [Head | Rest] ->
            bytes_list_to_bit_array(Rest, <<Acc/bitstring, Head>>)
    end.

-file("src/packkit/bzip2.gleam", 1121).
-spec rle1_decode(gleam@dict:dict(integer(), integer()), integer()) -> bitstring().
rle1_decode(Input, Length) ->
    _pipe = rle1_decode_loop(Input, 0, Length, -1, 0, []),
    _pipe@1 = lists:reverse(_pipe),
    bytes_list_to_bit_array(_pipe@1, <<>>).

-file("src/packkit/bzip2.gleam", 1176).
-spec new_reader(bitstring()) -> reader().
new_reader(Source) ->
    {reader, 0, 0, Source, false}.

-file("src/packkit/bzip2.gleam", 1180).
-spec refill(reader(), integer()) -> reader().
refill(Reader, Needed) ->
    case (erlang:element(2, Reader) >= Needed) orelse erlang:element(5, Reader) of
        true ->
            Reader;

        false ->
            case erlang:element(4, Reader) of
                <<B, Rest/binary>> ->
                    refill(
                        {reader,
                            erlang:element(2, Reader) + 8,
                            erlang:'bor'(
                                erlang:'bsl'(erlang:element(3, Reader), 8),
                                B
                            ),
                            Rest,
                            false},
                        Needed
                    );

                _ ->
                    {reader,
                        erlang:element(2, Reader),
                        erlang:element(3, Reader),
                        <<>>,
                        true}
            end
    end.

-file("src/packkit/bzip2.gleam", 1209).
-spec read_bits(reader(), integer()) -> {ok, {integer(), reader()}} |
    {error, packkit@error:codec_error()}.
read_bits(Reader, Count) ->
    Reader@1 = refill(Reader, Count),
    case erlang:element(2, Reader@1) < Count of
        true ->
            {error, {codec_invalid_data, <<"truncated bzip2 stream"/utf8>>}};

        false ->
            New_bits = erlang:element(2, Reader@1) - Count,
            Mask = erlang:'bsl'(1, Count) - 1,
            Value = erlang:'band'(
                erlang:'bsr'(erlang:element(3, Reader@1), New_bits),
                Mask
            ),
            Buffer_mask = erlang:'bsl'(1, New_bits) - 1,
            New_buffer = erlang:'band'(erlang:element(3, Reader@1), Buffer_mask),
            {ok,
                {Value,
                    {reader,
                        New_bits,
                        New_buffer,
                        erlang:element(4, Reader@1),
                        erlang:element(5, Reader@1)}}}
    end.

-file("src/packkit/bzip2.gleam", 328).
-spec read_symbol_groups(reader(), integer(), integer(), list(integer())) -> {ok,
        {list(integer()), reader()}} |
    {error, packkit@error:codec_error()}.
read_symbol_groups(Reader, Presence, Group, Acc) ->
    case Group >= 16 of
        true ->
            {ok, {lists:reverse(Acc), Reader}};

        false ->
            Bit_mask = erlang:'bsl'(1, 15 - Group),
            case erlang:'band'(Presence, Bit_mask) of
                0 ->
                    read_symbol_groups(Reader, Presence, Group + 1, Acc);

                _ ->
                    gleam@result:'try'(
                        read_bits(Reader, 16),
                        fun(_use0) ->
                            {Bits, Reader@1} = _use0,
                            Acc@1 = collect_group_bytes(Bits, Group, 0, Acc),
                            read_symbol_groups(
                                Reader@1,
                                Presence,
                                Group + 1,
                                Acc@1
                            )
                        end
                    )
            end
    end.

-file("src/packkit/bzip2.gleam", 321).
-spec read_symbol_map(reader()) -> {ok, {list(integer()), reader()}} |
    {error, packkit@error:codec_error()}.
read_symbol_map(Reader) ->
    gleam@result:'try'(
        read_bits(Reader, 16),
        fun(_use0) ->
            {Present_groups, Reader@1} = _use0,
            read_symbol_groups(Reader@1, Present_groups, 0, [])
        end
    ).

-file("src/packkit/bzip2.gleam", 410).
-spec read_unary(reader(), integer()) -> {ok, {integer(), reader()}} |
    {error, packkit@error:codec_error()}.
read_unary(Reader, Acc) ->
    gleam@result:'try'(
        read_bits(Reader, 1),
        fun(_use0) ->
            {Bit, Reader@1} = _use0,
            case Bit of
                0 ->
                    {ok, {Acc, Reader@1}};

                _ ->
                    read_unary(Reader@1, Acc + 1)
            end
        end
    ).

-file("src/packkit/bzip2.gleam", 387).
-spec read_selectors_loop(reader(), integer(), list(integer()), list(integer())) -> {ok,
        {list(integer()), reader()}} |
    {error, packkit@error:codec_error()}.
read_selectors_loop(Reader, Remaining, Stack, Acc) ->
    case Remaining of
        0 ->
            {ok, {lists:reverse(Acc), Reader}};

        _ ->
            gleam@result:'try'(
                read_unary(Reader, 0),
                fun(_use0) ->
                    {Index, Reader@1} = _use0,
                    gleam@bool:guard(
                        Index >= erlang:length(Stack),
                        {error,
                            {codec_invalid_data,
                                <<"bzip2 selector out of range"/utf8>>}},
                        fun() ->
                            {Picked, Remaining_stack} = pick_at(
                                Stack,
                                Index,
                                []
                            ),
                            Stack@1 = [Picked | Remaining_stack],
                            read_selectors_loop(
                                Reader@1,
                                Remaining - 1,
                                Stack@1,
                                [Picked | Acc]
                            )
                        end
                    )
                end
            )
    end.

-file("src/packkit/bzip2.gleam", 371).
-spec read_selectors(reader(), integer(), integer()) -> {ok,
        {list(integer()), reader()}} |
    {error, packkit@error:codec_error()}.
read_selectors(Reader, Remaining, Num_tables) ->
    Stack = init_selector_stack(Num_tables, 0, []),
    read_selectors_loop(Reader, Remaining, Stack, []).

-file("src/packkit/bzip2.gleam", 492).
-spec adjust_length(reader(), integer()) -> {ok, {integer(), reader()}} |
    {error, packkit@error:codec_error()}.
adjust_length(Reader, Current) ->
    gleam@result:'try'(
        read_bits(Reader, 1),
        fun(_use0) ->
            {Flag, Reader@1} = _use0,
            case Flag of
                0 ->
                    {ok, {Current, Reader@1}};

                _ ->
                    gleam@result:'try'(
                        read_bits(Reader@1, 1),
                        fun(_use0@1) ->
                            {Direction, Reader@2} = _use0@1,
                            Updated = case Direction of
                                0 ->
                                    Current + 1;

                                _ ->
                                    Current - 1
                            end,
                            adjust_length(Reader@2, Updated)
                        end
                    )
            end
        end
    ).

-file("src/packkit/bzip2.gleam", 952).
-spec walk_huffman(reader(), huffman_table(), integer(), integer()) -> {ok,
        {integer(), reader()}} |
    {error, packkit@error:codec_error()}.
walk_huffman(Reader, Table, Length, Code) ->
    case Length > erlang:element(3, Table) of
        true ->
            {error,
                {codec_invalid_data, <<"bzip2 huffman code overflow"/utf8>>}};

        false ->
            Limit_value = case gleam_stdlib:map_get(
                erlang:element(5, Table),
                Length
            ) of
                {ok, V} ->
                    V;

                {error, _} ->
                    -1
            end,
            case Code =< Limit_value of
                true ->
                    Base_value = case gleam_stdlib:map_get(
                        erlang:element(4, Table),
                        Length
                    ) of
                        {ok, V@1} ->
                            V@1;

                        {error, _} ->
                            0
                    end,
                    Index = Base_value + Code,
                    Symbol = case gleam_stdlib:map_get(
                        erlang:element(6, Table),
                        Index
                    ) of
                        {ok, V@2} ->
                            V@2;

                        {error, _} ->
                            -1
                    end,
                    case Symbol < 0 of
                        true ->
                            {error,
                                {codec_invalid_data,
                                    <<"bzip2 huffman symbol lookup failed"/utf8>>}};

                        false ->
                            {ok, {Symbol, Reader}}
                    end;

                false ->
                    gleam@result:'try'(
                        read_bits(Reader, 1),
                        fun(_use0) ->
                            {Bit, Reader@1} = _use0,
                            walk_huffman(
                                Reader@1,
                                Table,
                                Length + 1,
                                erlang:'bor'(erlang:'bsl'(Code, 1), Bit)
                            )
                        end
                    )
            end
    end.

-file("src/packkit/bzip2.gleam", 944).
-spec decode_one_symbol(reader(), huffman_table()) -> {ok,
        {integer(), reader()}} |
    {error, packkit@error:codec_error()}.
decode_one_symbol(Reader, Table) ->
    gleam@result:'try'(
        read_bits(Reader, erlang:element(2, Table)),
        fun(_use0) ->
            {Code, Reader@1} = _use0,
            walk_huffman(Reader@1, Table, erlang:element(2, Table), Code)
        end
    ).

-file("src/packkit/bzip2.gleam", 1274).
-spec rle1_loop(bitstring(), integer(), integer(), list(integer())) -> list(integer()).
rle1_loop(Bytes, Last, Run, Acc) ->
    case Bytes of
        <<B, Rest/binary>> ->
            case B =:= Last of
                true ->
                    case Run of
                        R when R < 4 ->
                            rle1_loop(Rest, Last, Run + 1, [B | Acc]);

                        R@1 when R@1 < 259 ->
                            case R@1 >= (4 + 254) of
                                true ->
                                    rle1_loop(Rest, -1, 0, [255 | Acc]);

                                false ->
                                    rle1_loop(Rest, Last, Run + 1, Acc)
                            end;

                        _ ->
                            rle1_loop(Rest, Last, Run + 1, Acc)
                    end;

                false ->
                    case Run >= 4 of
                        true ->
                            Extra = Run - 4,
                            rle1_loop(Rest, B, 1, [B, Extra | Acc]);

                        false ->
                            rle1_loop(Rest, B, 1, [B | Acc])
                    end
            end;

        _ ->
            case Run >= 4 of
                true ->
                    Extra@1 = Run - 4,
                    lists:reverse([Extra@1 | Acc]);

                false ->
                    lists:reverse(Acc)
            end
    end.

-file("src/packkit/bzip2.gleam", 1270).
-spec rle1_encode(bitstring()) -> list(integer()).
rle1_encode(Bytes) ->
    rle1_loop(Bytes, -1, 0, []).

-file("src/packkit/bzip2.gleam", 1326).
-spec list_range(integer(), integer(), list(integer())) -> list(integer()).
list_range(Low, High, Acc) ->
    case Low > High of
        true ->
            lists:reverse(Acc);

        false ->
            list_range(Low + 1, High, [Low | Acc])
    end.

-file("src/packkit/bzip2.gleam", 1362).
-spec byte_at_index(gleam@dict:dict(integer(), integer()), integer()) -> integer().
byte_at_index(Table, Index) ->
    case gleam_stdlib:map_get(Table, Index) of
        {ok, V} ->
            V;

        {error, _} ->
            0
    end.

-file("src/packkit/bzip2.gleam", 1369).
-spec mod_index(integer(), integer()) -> integer().
mod_index(Value, N) ->
    case N of
        0 ->
            0;

        _ ->
            R = Value - ((case N of
                0 -> 0;
                Gleam@denominator -> Value div Gleam@denominator
            end) * N),
            case R < 0 of
                true ->
                    R + N;

                false ->
                    R
            end
    end.

-file("src/packkit/bzip2.gleam", 1342).
-spec compare_loop(
    gleam@dict:dict(integer(), integer()),
    integer(),
    integer(),
    integer(),
    integer()
) -> gleam@order:order().
compare_loop(Table, N, A, B, Offset) ->
    case Offset >= N of
        true ->
            eq;

        false ->
            Ba = byte_at_index(Table, mod_index(A + Offset, N)),
            Bb = byte_at_index(Table, mod_index(B + Offset, N)),
            case Ba =:= Bb of
                true ->
                    compare_loop(Table, N, A, B, Offset + 1);

                false ->
                    gleam@int:compare(Ba, Bb)
            end
    end.

-file("src/packkit/bzip2.gleam", 1333).
-spec compare_rotations(
    gleam@dict:dict(integer(), integer()),
    integer(),
    integer(),
    integer()
) -> gleam@order:order().
compare_rotations(Table, N, A, B) ->
    compare_loop(Table, N, A, B, 0).

-file("src/packkit/bzip2.gleam", 1382).
-spec build_l_dict(
    list(integer()),
    gleam@dict:dict(integer(), integer()),
    integer(),
    integer(),
    gleam@dict:dict(integer(), integer())
) -> gleam@dict:dict(integer(), integer()).
build_l_dict(Sorted, Table, N, Index, Acc) ->
    case Sorted of
        [] ->
            Acc;

        [Head | Rest] ->
            Byte = byte_at_index(Table, mod_index((Head + N) - 1, N)),
            build_l_dict(
                Rest,
                Table,
                N,
                Index + 1,
                gleam@dict:insert(Acc, Index, Byte)
            )
    end.

-file("src/packkit/bzip2.gleam", 1398).
-spec find_index(list(integer()), integer(), integer()) -> integer().
find_index(Values, Target, Index) ->
    case Values of
        [Head | _] when Head =:= Target ->
            Index;

        [_ | Rest] ->
            find_index(Rest, Target, Index + 1);

        [] ->
            0
    end.

-file("src/packkit/bzip2.gleam", 1316).
-spec bwt_forward(list(integer()), integer()) -> {gleam@dict:dict(integer(), integer()),
    integer()}.
bwt_forward(Input, N) ->
    Table = list_to_dict(Input, 0, maps:new()),
    Indices = list_range(0, N - 1, []),
    Sorted = gleam@list:sort(
        Indices,
        fun(A, B) -> compare_rotations(Table, N, A, B) end
    ),
    L_dict = build_l_dict(Sorted, Table, N, 0, maps:new()),
    Orig_ptr = find_index(Sorted, 0, 0),
    {L_dict, Orig_ptr}.

-file("src/packkit/bzip2.gleam", 1408).
-spec sorted_unique_bytes(gleam@dict:dict(integer(), integer())) -> list(integer()).
sorted_unique_bytes(L_string) ->
    Unique_dict = gleam@dict:fold(
        L_string,
        maps:new(),
        fun(Acc, _, Value) -> gleam@dict:insert(Acc, Value, true) end
    ),
    Bytes = maps:keys(Unique_dict),
    gleam@list:sort(Bytes, fun gleam@int:compare/2).

-file("src/packkit/bzip2.gleam", 1441).
-spec find_and_pop(list(integer()), integer(), integer(), list(integer())) -> {integer(),
    list(integer())}.
find_and_pop(Stack, Target, Pos, Prefix) ->
    case Stack of
        [Head | Rest] when Head =:= Target ->
            {Pos, lists:append(lists:reverse(Prefix), Rest)};

        [Head@1 | Rest@1] ->
            find_and_pop(Rest@1, Target, Pos + 1, [Head@1 | Prefix]);

        [] ->
            {Pos, lists:reverse(Prefix)}
    end.

-file("src/packkit/bzip2.gleam", 1421).
-spec mtf_forward_loop(
    gleam@dict:dict(integer(), integer()),
    integer(),
    integer(),
    list(integer()),
    list(integer())
) -> list(integer()).
mtf_forward_loop(L_string, Index, Length, Stack, Acc) ->
    case Index >= Length of
        true ->
            lists:reverse(Acc);

        false ->
            Byte = byte_at_index(L_string, Index),
            {Pos, New_stack} = find_and_pop(Stack, Byte, 0, []),
            mtf_forward_loop(
                L_string,
                Index + 1,
                Length,
                [Byte | New_stack],
                [Pos | Acc]
            )
    end.

-file("src/packkit/bzip2.gleam", 1417).
-spec mtf_forward(gleam@dict:dict(integer(), integer()), list(integer())) -> list(integer()).
mtf_forward(L_string, Unique) ->
    mtf_forward_loop(L_string, 0, maps:size(L_string), Unique, []).

-file("src/packkit/bzip2.gleam", 1487).
-spec emit_runa_runb(integer(), list(integer())) -> list(integer()).
emit_runa_runb(Value, Acc) ->
    case Value of
        1 ->
            Acc;

        _ ->
            Bit = erlang:'band'(Value, 1),
            Next = erlang:'bsr'(Value, 1),
            emit_runa_runb(Next, [Bit | Acc])
    end.

-file("src/packkit/bzip2.gleam", 1480).
-spec flush_zero_run(integer(), list(integer())) -> list(integer()).
flush_zero_run(Count, Acc) ->
    case Count of
        0 ->
            Acc;

        _ ->
            emit_runa_runb(Count + 1, Acc)
    end.

-file("src/packkit/bzip2.gleam", 1461).
-spec rle2_loop(list(integer()), integer(), integer(), list(integer())) -> list(integer()).
rle2_loop(Mtf, Zero_run, Eob, Acc) ->
    case Mtf of
        [0 | Rest] ->
            rle2_loop(Rest, Zero_run + 1, Eob, Acc);

        [N | Rest@1] ->
            Acc@1 = flush_zero_run(Zero_run, Acc),
            rle2_loop(Rest@1, 0, Eob, [N + 1 | Acc@1]);

        [] ->
            Acc@2 = flush_zero_run(Zero_run, Acc),
            lists:reverse([Eob | Acc@2])
    end.

-file("src/packkit/bzip2.gleam", 1457).
-spec rle2_encode(list(integer()), integer()) -> list(integer()).
rle2_encode(Mtf, Eob) ->
    rle2_loop(Mtf, 0, Eob, []).

-file("src/packkit/bzip2.gleam", 1511).
-spec count_frequencies(list(integer()), gleam@dict:dict(integer(), integer())) -> gleam@dict:dict(integer(), integer()).
count_frequencies(Symbols, Acc) ->
    case Symbols of
        [] ->
            Acc;

        [Head | Rest] ->
            Current = case gleam_stdlib:map_get(Acc, Head) of
                {ok, V} ->
                    V;

                {error, _} ->
                    0
            end,
            count_frequencies(Rest, gleam@dict:insert(Acc, Head, Current + 1))
    end.

-file("src/packkit/bzip2.gleam", 1527).
-spec pad_min_frequencies(
    gleam@dict:dict(integer(), integer()),
    integer(),
    integer()
) -> gleam@dict:dict(integer(), integer()).
pad_min_frequencies(Freq, Symbol, Alphabet_size) ->
    case Symbol >= Alphabet_size of
        true ->
            Freq;

        false ->
            Freq@1 = case gleam_stdlib:map_get(Freq, Symbol) of
                {ok, _} ->
                    Freq;

                {error, _} ->
                    gleam@dict:insert(Freq, Symbol, 1)
            end,
            pad_min_frequencies(Freq@1, Symbol + 1, Alphabet_size)
    end.

-file("src/packkit/bzip2.gleam", 1564).
-spec flatten_frequencies(gleam@dict:dict(integer(), integer())) -> gleam@dict:dict(integer(), integer()).
flatten_frequencies(Freq) ->
    gleam@dict:fold(
        Freq,
        maps:new(),
        fun(Acc, Sym, Count) ->
            New_count = case Count of
                N when N =< 1 ->
                    1;

                N@1 ->
                    ((N@1 + 1) div 2) + 1
            end,
            gleam@dict:insert(Acc, Sym, New_count)
        end
    ).

-file("src/packkit/bzip2.gleam", 1577).
-spec max_length_in_dict(
    gleam@dict:dict(integer(), integer()),
    integer(),
    integer(),
    integer()
) -> integer().
max_length_in_dict(Lengths, Symbol, Alphabet_size, Best) ->
    case Symbol >= Alphabet_size of
        true ->
            Best;

        false ->
            Len = case gleam_stdlib:map_get(Lengths, Symbol) of
                {ok, V} ->
                    V;

                {error, _} ->
                    0
            end,
            Best@1 = case Len > Best of
                true ->
                    Len;

                false ->
                    Best
            end,
            max_length_in_dict(Lengths, Symbol + 1, Alphabet_size, Best@1)
    end.

-file("src/packkit/bzip2.gleam", 1614).
-spec build_leaves(
    gleam@dict:dict(integer(), integer()),
    integer(),
    integer(),
    list(huff_node())
) -> list(huff_node()).
build_leaves(Freq, Symbol, Alphabet_size, Acc) ->
    case Symbol >= Alphabet_size of
        true ->
            Acc;

        false ->
            Weight = case gleam_stdlib:map_get(Freq, Symbol) of
                {ok, V} ->
                    V;

                {error, _} ->
                    0
            end,
            build_leaves(
                Freq,
                Symbol + 1,
                Alphabet_size,
                [{huff_leaf, Symbol, Weight} | Acc]
            )
    end.

-file("src/packkit/bzip2.gleam", 1659).
-spec node_weight(huff_node()) -> integer().
node_weight(Node) ->
    case Node of
        {huff_leaf, _, W} ->
            W;

        {huff_internal, W@1, _, _} ->
            W@1
    end.

-file("src/packkit/bzip2.gleam", 1635).
-spec merge_nodes(list(huff_node())) -> huff_node().
merge_nodes(Nodes) ->
    case Nodes of
        [Single] ->
            Single;

        _ ->
            Sorted = gleam@list:sort(
                Nodes,
                fun(A, B) ->
                    gleam@int:compare(node_weight(A), node_weight(B))
                end
            ),
            case Sorted of
                [A@1, B@1 | Rest] ->
                    merge_nodes(
                        [{huff_internal,
                                node_weight(A@1) + node_weight(B@1),
                                A@1,
                                B@1} |
                            Rest]
                    );

                _ ->
                    {huff_leaf, 0, 0}
            end
    end.

-file("src/packkit/bzip2.gleam", 1666).
-spec collect_lengths(
    huff_node(),
    integer(),
    gleam@dict:dict(integer(), integer())
) -> gleam@dict:dict(integer(), integer()).
collect_lengths(Node, Depth, Acc) ->
    case Node of
        {huff_leaf, Symbol, _} ->
            Depth@1 = case Depth of
                0 ->
                    1;

                _ ->
                    Depth
            end,
            gleam@dict:insert(Acc, Symbol, Depth@1);

        {huff_internal, _, Left, Right} ->
            Acc@1 = collect_lengths(Left, Depth + 1, Acc),
            collect_lengths(Right, Depth + 1, Acc@1)
    end.

-file("src/packkit/bzip2.gleam", 1600).
-spec compute_huffman_lengths(gleam@dict:dict(integer(), integer()), integer()) -> gleam@dict:dict(integer(), integer()).
compute_huffman_lengths(Freq, Alphabet_size) ->
    Nodes = build_leaves(Freq, 0, Alphabet_size, []),
    Merged_root = merge_nodes(Nodes),
    collect_lengths(Merged_root, 0, maps:new()).

-file("src/packkit/bzip2.gleam", 1547).
-spec package_merge_lengths(
    gleam@dict:dict(integer(), integer()),
    integer(),
    integer()
) -> gleam@dict:dict(integer(), integer()).
package_merge_lengths(Freq, Alphabet_size, Max_length) ->
    Lengths = compute_huffman_lengths(Freq, Alphabet_size),
    case max_length_in_dict(Lengths, 0, Alphabet_size, 0) > Max_length of
        false ->
            Lengths;

        true ->
            package_merge_lengths(
                flatten_frequencies(Freq),
                Alphabet_size,
                Max_length
            )
    end.

-file("src/packkit/bzip2.gleam", 1502).
-spec build_huffman_lengths(list(integer()), integer()) -> gleam@dict:dict(integer(), integer()).
build_huffman_lengths(Symbols, Alphabet_size) ->
    Freq = count_frequencies(Symbols, maps:new()),
    Freq@1 = pad_min_frequencies(Freq, 0, Alphabet_size),
    package_merge_lengths(Freq@1, Alphabet_size, 17).

-file("src/packkit/bzip2.gleam", 1700).
-spec assign_codes(
    list({integer(), integer()}),
    integer(),
    integer(),
    gleam@dict:dict(integer(), integer())
) -> gleam@dict:dict(integer(), integer()).
assign_codes(Pairs, Code, Prev_length, Acc) ->
    case Pairs of
        [] ->
            Acc;

        [{Sym, Length} | Rest] ->
            Code@1 = case Prev_length of
                0 ->
                    0;

                _ ->
                    erlang:'bsl'(Code, Length - Prev_length)
            end,
            assign_codes(
                Rest,
                Code@1 + 1,
                Length,
                gleam@dict:insert(Acc, Sym, Code@1)
            )
    end.

-file("src/packkit/bzip2.gleam", 1688).
-spec canonical_codes(gleam@dict:dict(integer(), integer())) -> gleam@dict:dict(integer(), integer()).
canonical_codes(Lengths) ->
    Pairs = maps:to_list(Lengths),
    Sorted = gleam@list:sort(
        Pairs,
        fun(A, B) ->
            case gleam@int:compare(erlang:element(2, A), erlang:element(2, B)) of
                eq ->
                    gleam@int:compare(
                        erlang:element(1, A),
                        erlang:element(1, B)
                    );

                Other ->
                    Other
            end
        end
    ),
    assign_codes(Sorted, 0, 0, maps:new()).

-file("src/packkit/bzip2.gleam", 1727).
-spec bytes_to_group_dict(
    list(integer()),
    gleam@dict:dict(integer(), list(integer()))
) -> gleam@dict:dict(integer(), list(integer())).
bytes_to_group_dict(Bytes, Acc) ->
    case Bytes of
        [] ->
            Acc;

        [B | Rest] ->
            Group = B div 16,
            Existing = case gleam_stdlib:map_get(Acc, Group) of
                {ok, V} ->
                    V;

                {error, _} ->
                    []
            end,
            bytes_to_group_dict(
                Rest,
                gleam@dict:insert(Acc, Group, [B | Existing])
            )
    end.

-file("src/packkit/bzip2.gleam", 1744).
-spec compute_group_used(
    gleam@dict:dict(integer(), list(integer())),
    integer(),
    gleam@dict:dict(integer(), boolean())
) -> gleam@dict:dict(integer(), boolean()).
compute_group_used(Group_dict, Group, Acc) ->
    case Group >= 16 of
        true ->
            Acc;

        false ->
            Used = case gleam_stdlib:map_get(Group_dict, Group) of
                {ok, _} ->
                    true;

                {error, _} ->
                    false
            end,
            compute_group_used(
                Group_dict,
                Group + 1,
                gleam@dict:insert(Acc, Group, Used)
            )
    end.

-file("src/packkit/bzip2.gleam", 1935).
-spec new_writer() -> writer().
new_writer() ->
    {writer, [], 0, 0}.

-file("src/packkit/bzip2.gleam", 1955).
-spec mask_for(integer()) -> integer().
mask_for(Count) ->
    erlang:'bsl'(1, Count) - 1.

-file("src/packkit/bzip2.gleam", 1959).
-spec flush_writer_full_bytes(writer()) -> writer().
flush_writer_full_bytes(Writer) ->
    case erlang:element(4, Writer) >= 8 of
        false ->
            Writer;

        true ->
            Shift = erlang:element(4, Writer) - 8,
            Byte = erlang:'band'(
                erlang:'bsr'(erlang:element(3, Writer), Shift),
                16#FF
            ),
            New_buffer = erlang:'band'(
                erlang:element(3, Writer),
                mask_for(Shift)
            ),
            flush_writer_full_bytes(
                {writer, [Byte | erlang:element(2, Writer)], New_buffer, Shift}
            )
    end.

-file("src/packkit/bzip2.gleam", 1939).
-spec write_bits_msb(writer(), integer(), integer()) -> writer().
write_bits_msb(Writer, Value, Count) ->
    case Count of
        0 ->
            Writer;

        _ ->
            Masked = erlang:'band'(Value, mask_for(Count)),
            Buffer = erlang:'bor'(
                erlang:'bsl'(erlang:element(3, Writer), Count),
                Masked
            ),
            flush_writer_full_bytes(
                {writer,
                    erlang:element(2, Writer),
                    Buffer,
                    erlang:element(4, Writer) + Count}
            )
    end.

-file("src/packkit/bzip2.gleam", 1761).
-spec emit_group_high(
    writer(),
    gleam@dict:dict(integer(), boolean()),
    integer()
) -> writer().
emit_group_high(Writer, Group_used, Group) ->
    case Group >= 16 of
        true ->
            Writer;

        false ->
            Bit = case gleam_stdlib:map_get(Group_used, Group) of
                {ok, true} ->
                    1;

                _ ->
                    0
            end,
            emit_group_high(
                write_bits_msb(Writer, Bit, 1),
                Group_used,
                Group + 1
            )
    end.

-file("src/packkit/bzip2.gleam", 1801).
-spec emit_bitmap_for_group(writer(), list(integer()), integer(), integer()) -> writer().
emit_bitmap_for_group(Writer, Bytes, Group, Offset) ->
    case Offset >= 16 of
        true ->
            Writer;

        false ->
            Target = (Group * 16) + Offset,
            Bit = case gleam@list:contains(Bytes, Target) of
                true ->
                    1;

                false ->
                    0
            end,
            emit_bitmap_for_group(
                write_bits_msb(Writer, Bit, 1),
                Bytes,
                Group,
                Offset + 1
            )
    end.

-file("src/packkit/bzip2.gleam", 1778).
-spec emit_group_bitmaps(
    writer(),
    gleam@dict:dict(integer(), boolean()),
    gleam@dict:dict(integer(), list(integer())),
    integer()
) -> writer().
emit_group_bitmaps(Writer, Group_used, Group_dict, Group) ->
    case Group >= 16 of
        true ->
            Writer;

        false ->
            case gleam_stdlib:map_get(Group_used, Group) of
                {ok, true} ->
                    Bytes = case gleam_stdlib:map_get(Group_dict, Group) of
                        {ok, V} ->
                            V;

                        {error, _} ->
                            []
                    end,
                    Writer@1 = emit_bitmap_for_group(Writer, Bytes, Group, 0),
                    emit_group_bitmaps(
                        Writer@1,
                        Group_used,
                        Group_dict,
                        Group + 1
                    );

                _ ->
                    emit_group_bitmaps(
                        Writer,
                        Group_used,
                        Group_dict,
                        Group + 1
                    )
            end
    end.

-file("src/packkit/bzip2.gleam", 1720).
-spec emit_symbol_map(writer(), list(integer())) -> writer().
emit_symbol_map(Writer, Unique) ->
    Group_dict = bytes_to_group_dict(Unique, maps:new()),
    Group_used = compute_group_used(Group_dict, 0, maps:new()),
    Writer@1 = emit_group_high(Writer, Group_used, 0),
    emit_group_bitmaps(Writer@1, Group_used, Group_dict, 0).

-file("src/packkit/bzip2.gleam", 1831).
-spec emit_selectors_loop(writer(), integer()) -> writer().
emit_selectors_loop(Writer, Remaining) ->
    case Remaining of
        0 ->
            Writer;

        _ ->
            emit_selectors_loop(write_bits_msb(Writer, 0, 1), Remaining - 1)
    end.

-file("src/packkit/bzip2.gleam", 1825).
-spec emit_selectors(writer(), integer()) -> writer().
emit_selectors(Writer, Num_groups) ->
    emit_selectors_loop(Writer, Num_groups).

-file("src/packkit/bzip2.gleam", 1883).
-spec emit_length_diff(writer(), integer(), integer()) -> writer().
emit_length_diff(Writer, Previous, Target) ->
    case Target =:= Previous of
        true ->
            write_bits_msb(Writer, 0, 1);

        false ->
            case Target > Previous of
                true ->
                    Writer@1 = write_bits_msb(Writer, 1, 1),
                    Writer@2 = write_bits_msb(Writer@1, 0, 1),
                    emit_length_diff(Writer@2, Previous + 1, Target);

                false ->
                    Writer@3 = write_bits_msb(Writer, 1, 1),
                    Writer@4 = write_bits_msb(Writer@3, 1, 1),
                    emit_length_diff(Writer@4, Previous - 1, Target)
            end
    end.

-file("src/packkit/bzip2.gleam", 1863).
-spec emit_lengths_diff(
    writer(),
    gleam@dict:dict(integer(), integer()),
    integer(),
    integer(),
    integer()
) -> writer().
emit_lengths_diff(Writer, Lengths, Symbol, Alphabet_size, Previous) ->
    case Symbol >= Alphabet_size of
        true ->
            Writer;

        false ->
            Target = case gleam_stdlib:map_get(Lengths, Symbol) of
                {ok, V} ->
                    V;

                {error, _} ->
                    Previous
            end,
            Writer@1 = emit_length_diff(Writer, Previous, Target),
            emit_lengths_diff(
                Writer@1,
                Lengths,
                Symbol + 1,
                Alphabet_size,
                Target
            )
    end.

-file("src/packkit/bzip2.gleam", 1847).
-spec emit_table_lengths(
    writer(),
    gleam@dict:dict(integer(), integer()),
    integer()
) -> writer().
emit_table_lengths(Writer, Lengths, Alphabet_size) ->
    First = case gleam_stdlib:map_get(Lengths, 0) of
        {ok, V} ->
            V;

        {error, _} ->
            1
    end,
    Writer@1 = write_bits_msb(Writer, First, 5),
    emit_lengths_diff(Writer@1, Lengths, 0, Alphabet_size, First).

-file("src/packkit/bzip2.gleam", 1838).
-spec emit_two_tables(
    writer(),
    gleam@dict:dict(integer(), integer()),
    integer()
) -> writer().
emit_two_tables(Writer, Lengths, Alphabet_size) ->
    Writer@1 = emit_table_lengths(Writer, Lengths, Alphabet_size),
    emit_table_lengths(Writer@1, Lengths, Alphabet_size).

-file("src/packkit/bzip2.gleam", 1902).
-spec emit_huffman_data(
    writer(),
    list(integer()),
    gleam@dict:dict(integer(), integer()),
    gleam@dict:dict(integer(), integer())
) -> writer().
emit_huffman_data(Writer, Symbols, Lengths, Codes) ->
    case Symbols of
        [] ->
            Writer;

        [Head | Rest] ->
            Code = case gleam_stdlib:map_get(Codes, Head) of
                {ok, V} ->
                    V;

                {error, _} ->
                    0
            end,
            Length = case gleam_stdlib:map_get(Lengths, Head) of
                {ok, V@1} ->
                    V@1;

                {error, _} ->
                    1
            end,
            emit_huffman_data(
                write_bits_msb(Writer, Code, Length),
                Rest,
                Lengths,
                Codes
            )
    end.

-file("src/packkit/bzip2.gleam", 1989).
-spec list_to_bit_array(list(integer()), bitstring()) -> bitstring().
list_to_bit_array(Values, Acc) ->
    case Values of
        [] ->
            Acc;

        [Head | Rest] ->
            list_to_bit_array(Rest, <<Acc/bitstring, Head>>)
    end.

-file("src/packkit/bzip2.gleam", 1976).
-spec flush_writer_msb(writer()) -> bitstring().
flush_writer_msb(Writer) ->
    Writer@1 = case erlang:element(4, Writer) of
        0 ->
            Writer;

        _ ->
            Pad = 8 - erlang:element(4, Writer),
            Shifted = erlang:'bsl'(erlang:element(3, Writer), Pad),
            Byte = erlang:'band'(Shifted, 16#FF),
            {writer, [Byte | erlang:element(2, Writer)], 0, 0}
    end,
    list_to_bit_array(lists:reverse(erlang:element(2, Writer@1)), <<>>).

-file("src/packkit/bzip2.gleam", 79).
-spec write_eos_marker(writer(), integer()) -> writer().
write_eos_marker(Writer, Stream_crc) ->
    _pipe = Writer,
    _pipe@1 = write_bits_msb(_pipe, 16#177245, 24),
    _pipe@2 = write_bits_msb(_pipe@1, 16#385090, 24),
    write_bits_msb(_pipe@2, Stream_crc, 32).

-file("src/packkit/bzip2.gleam", 471).
-spec read_table_lengths(reader(), integer(), integer(), list(integer())) -> {ok,
        {list(integer()), reader()}} |
    {error, packkit@error:codec_error()}.
read_table_lengths(Reader, Remaining, Current, Acc) ->
    case Remaining of
        0 ->
            {ok, {lists:reverse(Acc), Reader}};

        _ ->
            gleam@result:'try'(
                adjust_length(Reader, Current),
                fun(_use0) ->
                    {Current@1, Reader@1} = _use0,
                    gleam@bool:guard(
                        (Current@1 < 1) orelse (Current@1 > 20),
                        {error,
                            {codec_invalid_data,
                                <<"bzip2 huffman length out of range"/utf8>>}},
                        fun() ->
                            read_table_lengths(
                                Reader@1,
                                Remaining - 1,
                                Current@1,
                                [Current@1 | Acc]
                            )
                        end
                    )
                end
            )
    end.

-file("src/packkit/bzip2.gleam", 510).
-spec build_huffman_table(list(integer())) -> {ok, huffman_table()} |
    {error, packkit@error:codec_error()}.
build_huffman_table(Lengths) ->
    Pairs = enumerate_lengths(Lengths, 0, []),
    Min_length = find_min_length(Lengths, 21),
    Max_length = find_max_length(Lengths, 0),
    gleam@bool:guard(
        ((Min_length < 1) orelse (Max_length > 20)) orelse (Max_length < 1),
        {error,
            {codec_invalid_data, <<"bzip2 huffman length out of range"/utf8>>}},
        fun() ->
            Sorted = sort_pairs_by_length(Pairs, Min_length, Max_length, []),
            Acc = {build_acc, maps:new(), maps:new(), maps:new(), 0, 0, 0},
            Acc@1 = canonical_loop(Sorted, Acc),
            Final_limit = case erlang:element(7, Acc@1) of
                0 ->
                    erlang:element(3, Acc@1);

                _ ->
                    gleam@dict:insert(
                        erlang:element(3, Acc@1),
                        erlang:element(7, Acc@1),
                        erlang:element(6, Acc@1) - 1
                    )
            end,
            {ok,
                {huffman_table,
                    Min_length,
                    Max_length,
                    erlang:element(2, Acc@1),
                    Final_limit,
                    erlang:element(4, Acc@1)}}
        end
    ).

-file("src/packkit/bzip2.gleam", 449).
-spec read_huffman_tables_loop(
    reader(),
    integer(),
    integer(),
    list(huffman_table())
) -> {ok, {list(huffman_table()), reader()}} |
    {error, packkit@error:codec_error()}.
read_huffman_tables_loop(Reader, Remaining, Alphabet_size, Acc) ->
    case Remaining of
        0 ->
            {ok, {lists:reverse(Acc), Reader}};

        _ ->
            gleam@result:'try'(
                read_bits(Reader, 5),
                fun(_use0) ->
                    {Initial, Reader@1} = _use0,
                    gleam@result:'try'(
                        read_table_lengths(Reader@1, Alphabet_size, Initial, []),
                        fun(_use0@1) ->
                            {Lengths, Reader@2} = _use0@1,
                            gleam@result:'try'(
                                build_huffman_table(Lengths),
                                fun(Table) ->
                                    read_huffman_tables_loop(
                                        Reader@2,
                                        Remaining - 1,
                                        Alphabet_size,
                                        [Table | Acc]
                                    )
                                end
                            )
                        end
                    )
                end
            )
    end.

-file("src/packkit/bzip2.gleam", 441).
-spec read_huffman_tables(reader(), integer(), integer()) -> {ok,
        {list(huffman_table()), reader()}} |
    {error, packkit@error:codec_error()}.
read_huffman_tables(Reader, Count, Alphabet_size) ->
    read_huffman_tables_loop(Reader, Count, Alphabet_size, []).

-file("src/packkit/bzip2.gleam", 768).
-spec decode_huffman_step(
    reader(),
    gleam@dict:dict(integer(), huffman_table()),
    gleam@dict:dict(integer(), integer()),
    integer(),
    list(integer()),
    integer(),
    integer(),
    integer(),
    list(integer()),
    integer(),
    packkit@limit:limits()
) -> {ok, huffman_stream_step()} | {error, packkit@error:codec_error()}.
decode_huffman_step(
    Reader,
    Tables,
    Selectors,
    Eob,
    Mtf,
    Symbol_count,
    Pending_run,
    Run_weight,
    Out_rev,
    Out_len,
    Limits
) ->
    Group_index = case 50 of
        0 -> 0;
        Gleam@denominator -> Symbol_count div Gleam@denominator
    end,
    Selector = case gleam_stdlib:map_get(Selectors, Group_index) of
        {ok, V} ->
            V;

        {error, _} ->
            0
    end,
    Table = case gleam_stdlib:map_get(Tables, Selector) of
        {ok, V@1} ->
            V@1;

        {error, _} ->
            {huffman_table, 1, 1, maps:new(), maps:new(), maps:new()}
    end,
    gleam@result:'try'(
        decode_one_symbol(Reader, Table),
        fun(_use0) ->
            {Symbol, Reader@1} = _use0,
            case Symbol of
                S when S =:= Eob ->
                    gleam@result:'try'(
                        flush_run(Out_rev, Out_len, Mtf, Pending_run, Limits),
                        fun(_use0@1) ->
                            {Out_rev@1, Out_len@1} = _use0@1,
                            {ok,
                                {huffman_stream_done,
                                    Out_rev@1,
                                    Out_len@1,
                                    Reader@1}}
                        end
                    );

                0 ->
                    {ok,
                        {huffman_stream_continue,
                            Reader@1,
                            Mtf,
                            Symbol_count + 1,
                            Pending_run + Run_weight,
                            Run_weight * 2,
                            Out_rev,
                            Out_len}};

                1 ->
                    {ok,
                        {huffman_stream_continue,
                            Reader@1,
                            Mtf,
                            Symbol_count + 1,
                            Pending_run + (2 * Run_weight),
                            Run_weight * 2,
                            Out_rev,
                            Out_len}};

                Other ->
                    gleam@result:'try'(
                        flush_run(Out_rev, Out_len, Mtf, Pending_run, Limits),
                        fun(_use0@2) ->
                            {Out_rev@2, Out_len@2} = _use0@2,
                            Mtf_index = Other - 1,
                            gleam@result:'try'(
                                mtf_pick_byte(Mtf, Mtf_index),
                                fun(_use0@3) ->
                                    {Byte, New_mtf} = _use0@3,
                                    gleam@result:'try'(
                                        emit_byte(
                                            Out_rev@2,
                                            Out_len@2,
                                            Byte,
                                            Limits
                                        ),
                                        fun(_use0@4) ->
                                            {Out_rev@3, Out_len@3} = _use0@4,
                                            {ok,
                                                {huffman_stream_continue,
                                                    Reader@1,
                                                    New_mtf,
                                                    Symbol_count + 1,
                                                    0,
                                                    1,
                                                    Out_rev@3,
                                                    Out_len@3}}
                                        end
                                    )
                                end
                            )
                        end
                    )
            end
        end
    ).

-file("src/packkit/bzip2.gleam", 693).
-spec decode_huffman_stream(
    reader(),
    gleam@dict:dict(integer(), huffman_table()),
    gleam@dict:dict(integer(), integer()),
    integer(),
    list(integer()),
    integer(),
    integer(),
    integer(),
    integer(),
    list(integer()),
    integer(),
    packkit@limit:limits()
) -> {ok, {gleam@dict:dict(integer(), integer()), integer(), reader()}} |
    {error, packkit@error:codec_error()}.
decode_huffman_stream(
    Reader,
    Tables,
    Selectors,
    Eob,
    Mtf,
    Num_syms,
    Symbol_count,
    Pending_run,
    Run_weight,
    Out_rev,
    Out_len,
    Limits
) ->
    case decode_huffman_step(
        Reader,
        Tables,
        Selectors,
        Eob,
        Mtf,
        Symbol_count,
        Pending_run,
        Run_weight,
        Out_rev,
        Out_len,
        Limits
    ) of
        {error, Err} ->
            {error, Err};

        {ok, {huffman_stream_done, Final_out_rev, Final_out_len, Final_reader}} ->
            _ = Num_syms,
            L_string = list_to_indexed_dict(
                lists:reverse(Final_out_rev),
                0,
                maps:new()
            ),
            {ok, {L_string, Final_out_len, Final_reader}};

        {ok,
            {huffman_stream_continue,
                Next_reader,
                Next_mtf,
                Next_symbol_count,
                Next_pending_run,
                Next_run_weight,
                Next_out_rev,
                Next_out_len}} ->
            decode_huffman_stream(
                Next_reader,
                Tables,
                Selectors,
                Eob,
                Next_mtf,
                Num_syms,
                Next_symbol_count,
                Next_pending_run,
                Next_run_weight,
                Next_out_rev,
                Next_out_len,
                Limits
            )
    end.

-file("src/packkit/bzip2.gleam", 238).
-spec decode_block(reader(), packkit@limit:limits()) -> {ok,
        {bitstring(), integer(), reader()}} |
    {error, packkit@error:codec_error()}.
decode_block(Reader, Limits) ->
    gleam@result:'try'(
        read_bits(Reader, 32),
        fun(_use0) ->
            {Crc, Reader@1} = _use0,
            gleam@result:'try'(
                read_bits(Reader@1, 1),
                fun(_use0@1) ->
                    {Randomized, Reader@2} = _use0@1,
                    gleam@bool:guard(
                        Randomized /= 0,
                        {error,
                            {codec_invalid_data,
                                <<"randomized bzip2 blocks are not supported"/utf8>>}},
                        fun() ->
                            gleam@result:'try'(
                                read_bits(Reader@2, 24),
                                fun(_use0@2) ->
                                    {Orig_ptr, Reader@3} = _use0@2,
                                    gleam@result:'try'(
                                        read_symbol_map(Reader@3),
                                        fun(_use0@3) ->
                                            {Symbols, Reader@4} = _use0@3,
                                            Num_syms = erlang:length(Symbols),
                                            gleam@bool:guard(
                                                Num_syms =:= 0,
                                                {error,
                                                    {codec_invalid_data,
                                                        <<"empty bzip2 symbol map"/utf8>>}},
                                                fun() ->
                                                    Alphabet_size = Num_syms + 2,
                                                    gleam@result:'try'(
                                                        read_bits(Reader@4, 3),
                                                        fun(_use0@4) ->
                                                            {Num_tables,
                                                                Reader@5} = _use0@4,
                                                            gleam@bool:guard(
                                                                (Num_tables < 2)
                                                                orelse (Num_tables
                                                                > 6),
                                                                {error,
                                                                    {codec_invalid_data,
                                                                        <<"invalid bzip2 huffman table count"/utf8>>}},
                                                                fun() ->
                                                                    gleam@result:'try'(
                                                                        read_bits(
                                                                            Reader@5,
                                                                            15
                                                                        ),
                                                                        fun(
                                                                            _use0@5
                                                                        ) ->
                                                                            {Num_selectors,
                                                                                Reader@6} = _use0@5,
                                                                            gleam@bool:guard(
                                                                                (Num_selectors
                                                                                =< 0)
                                                                                orelse (Num_selectors
                                                                                > 18002),
                                                                                {error,
                                                                                    {codec_invalid_data,
                                                                                        <<"invalid bzip2 selector count"/utf8>>}},
                                                                                fun(
                                                                                    
                                                                                ) ->
                                                                                    gleam@result:'try'(
                                                                                        read_selectors(
                                                                                            Reader@6,
                                                                                            Num_selectors,
                                                                                            Num_tables
                                                                                        ),
                                                                                        fun(
                                                                                            _use0@6
                                                                                        ) ->
                                                                                            {Selectors,
                                                                                                Reader@7} = _use0@6,
                                                                                            gleam@result:'try'(
                                                                                                read_huffman_tables(
                                                                                                    Reader@7,
                                                                                                    Num_tables,
                                                                                                    Alphabet_size
                                                                                                ),
                                                                                                fun(
                                                                                                    _use0@7
                                                                                                ) ->
                                                                                                    {Tables,
                                                                                                        Reader@8} = _use0@7,
                                                                                                    Tables_arr = list_to_dict(
                                                                                                        Tables,
                                                                                                        0,
                                                                                                        maps:new(
                                                                                                            
                                                                                                        )
                                                                                                    ),
                                                                                                    Selectors_arr = list_to_dict(
                                                                                                        Selectors,
                                                                                                        0,
                                                                                                        maps:new(
                                                                                                            
                                                                                                        )
                                                                                                    ),
                                                                                                    Eob = Alphabet_size
                                                                                                    - 1,
                                                                                                    gleam@result:'try'(
                                                                                                        decode_huffman_stream(
                                                                                                            Reader@8,
                                                                                                            Tables_arr,
                                                                                                            Selectors_arr,
                                                                                                            Eob,
                                                                                                            Symbols,
                                                                                                            Num_syms,
                                                                                                            0,
                                                                                                            0,
                                                                                                            1,
                                                                                                            [],
                                                                                                            0,
                                                                                                            Limits
                                                                                                        ),
                                                                                                        fun(
                                                                                                            _use0@8
                                                                                                        ) ->
                                                                                                            {L_string,
                                                                                                                L_len,
                                                                                                                Reader@9} = _use0@8,
                                                                                                            gleam@bool:guard(
                                                                                                                Orig_ptr
                                                                                                                >= L_len,
                                                                                                                {error,
                                                                                                                    {codec_invalid_data,
                                                                                                                        <<"bzip2 BWT origin out of range"/utf8>>}},
                                                                                                                fun(
                                                                                                                    
                                                                                                                ) ->
                                                                                                                    Bwt_dict = inverse_bwt(
                                                                                                                        L_string,
                                                                                                                        L_len,
                                                                                                                        Orig_ptr
                                                                                                                    ),
                                                                                                                    Plain = rle1_decode(
                                                                                                                        Bwt_dict,
                                                                                                                        L_len
                                                                                                                    ),
                                                                                                                    {ok,
                                                                                                                        {Plain,
                                                                                                                            Crc,
                                                                                                                            Reader@9}}
                                                                                                                end
                                                                                                            )
                                                                                                        end
                                                                                                    )
                                                                                                end
                                                                                            )
                                                                                        end
                                                                                    )
                                                                                end
                                                                            )
                                                                        end
                                                                    )
                                                                end
                                                            )
                                                        end
                                                    )
                                                end
                                            )
                                        end
                                    )
                                end
                            )
                        end
                    )
                end
            )
        end
    ).

-file("src/packkit/bzip2.gleam", 155).
-spec decode_blocks_with_remainder(
    reader(),
    bitstring(),
    integer(),
    packkit@limit:limits()
) -> {ok, {bitstring(), bitstring()}} | {error, packkit@error:codec_error()}.
decode_blocks_with_remainder(Reader, Output, Combined_crc, Limits) ->
    gleam@result:'try'(
        read_bits(Reader, 24),
        fun(_use0) ->
            {Magic_high, Reader@1} = _use0,
            gleam@result:'try'(
                read_bits(Reader@1, 24),
                fun(_use0@1) ->
                    {Magic_low, Reader@2} = _use0@1,
                    case {Magic_high, Magic_low} of
                        {H, L} when (H =:= 16#314159) andalso (L =:= 16#265359) ->
                            gleam@result:'try'(
                                decode_block(Reader@2, Limits),
                                fun(_use0@2) ->
                                    {Block_bytes, Block_crc, Reader@3} = _use0@2,
                                    gleam@result:'try'(
                                        verify_block_crc(Block_bytes, Block_crc),
                                        fun(_) ->
                                            Combined_crc@1 = combine_block_crc(
                                                Combined_crc,
                                                Block_crc
                                            ),
                                            gleam@result:'try'(
                                                append_with_limit(
                                                    Output,
                                                    Block_bytes,
                                                    Limits
                                                ),
                                                fun(New_output) ->
                                                    decode_blocks_with_remainder(
                                                        Reader@3,
                                                        New_output,
                                                        Combined_crc@1,
                                                        Limits
                                                    )
                                                end
                                            )
                                        end
                                    )
                                end
                            );

                        {H@1, L@1} when (H@1 =:= 16#177245) andalso (L@1 =:= 16#385090) ->
                            gleam@result:'try'(
                                read_bits(Reader@2, 32),
                                fun(_use0@3) ->
                                    {Stream_crc, Reader@4} = _use0@3,
                                    case Stream_crc =:= Combined_crc of
                                        true ->
                                            {ok,
                                                {Output,
                                                    byte_aligned_remainder(
                                                        Reader@4
                                                    )}};

                                        false ->
                                            {error,
                                                {codec_invalid_data,
                                                    <<"bzip2 stream CRC mismatch"/utf8>>}}
                                    end
                                end
                            );

                        {_, _} ->
                            {error,
                                {codec_invalid_data,
                                    <<"unexpected bzip2 block marker"/utf8>>}}
                    end
                end
            )
        end
    ).

-file("src/packkit/bzip2.gleam", 113).
-spec decode_streams_loop(
    bitstring(),
    bitstring(),
    integer(),
    packkit@limit:limits()
) -> {ok, bitstring()} | {error, packkit@error:codec_error()}.
decode_streams_loop(Bytes, Acc, Accumulated_size, Limits) ->
    gleam@result:'try'(
        parse_stream_header(Bytes),
        fun(Rest) ->
            gleam@result:'try'(
                decode_blocks_with_remainder(new_reader(Rest), <<>>, 0, Limits),
                fun(_use0) ->
                    {Payload, After} = _use0,
                    Next_size = Accumulated_size + erlang:byte_size(Payload),
                    case Next_size > packkit@limit:max_output_bytes(Limits) of
                        true ->
                            {error,
                                {codec_limit_exceeded,
                                    <<"max_output_bytes"/utf8>>,
                                    Next_size}};

                        false ->
                            Acc@1 = gleam_stdlib:bit_array_concat(
                                [Acc, Payload]
                            ),
                            case erlang:byte_size(After) of
                                0 ->
                                    {ok, Acc@1};

                                _ ->
                                    decode_streams_loop(
                                        After,
                                        Acc@1,
                                        Next_size,
                                        Limits
                                    )
                            end
                    end
                end
            )
        end
    ).

-file("src/packkit/bzip2.gleam", 98).
?DOC(
    " Decode a bzip2 stream using explicit `Limits`.\n"
    "\n"
    " Handles multi-stream `.bz2` files (the `bzcat`-style concatenation\n"
    " of independent bzip2 streams).  When the end-of-stream marker for\n"
    " one stream is reached, the decoder aligns to the next byte\n"
    " boundary and looks for another `\"BZh\"` magic; if present, the\n"
    " next stream's payload is appended to the accumulated output.\n"
).
-spec decode_with_limits(bitstring(), packkit@limit:limits()) -> {ok,
        bitstring()} |
    {error, packkit@error:codec_error()}.
decode_with_limits(Bytes, Limits) ->
    gleam@bool:guard(
        erlang:byte_size(Bytes) > packkit@limit:max_input_bytes(Limits),
        {error,
            {codec_limit_exceeded,
                <<"max_input_bytes"/utf8>>,
                erlang:byte_size(Bytes)}},
        fun() -> decode_streams_loop(Bytes, <<>>, 0, Limits) end
    ).

-file("src/packkit/bzip2.gleam", 87).
?DOC(" Decode a bzip2 stream using the shared default `Limits`.\n").
-spec decode(bitstring()) -> {ok, bitstring()} |
    {error, packkit@error:codec_error()}.
decode(Bytes) ->
    decode_with_limits(Bytes, packkit@limit:default()).

-file("src/packkit/bzip2.gleam", 1238).
-spec encode_block(writer(), bitstring()) -> writer().
encode_block(Writer, Bytes) ->
    Block_crc = packkit@checksum:bzip2_crc32(Bytes),
    Rle1 = rle1_encode(Bytes),
    N = erlang:length(Rle1),
    {L_string, Orig_ptr} = bwt_forward(Rle1, N),
    Unique = sorted_unique_bytes(L_string),
    Mtf_indices = mtf_forward(L_string, Unique),
    Symbols = rle2_encode(Mtf_indices, erlang:length(Unique) + 1),
    Alphabet_size = erlang:length(Unique) + 2,
    Lengths = build_huffman_lengths(Symbols, Alphabet_size),
    Codes = canonical_codes(Lengths),
    Num_groups = case erlang:length(Symbols) of
        0 ->
            1;

        Sn ->
            case 50 of
                0 -> 0;
                Gleam@denominator -> ((Sn + 50) - 1) div Gleam@denominator
            end
    end,
    Writer@1 = begin
        _pipe = Writer,
        _pipe@1 = write_bits_msb(_pipe, 16#314159, 24),
        _pipe@2 = write_bits_msb(_pipe@1, 16#265359, 24),
        _pipe@3 = write_bits_msb(_pipe@2, Block_crc, 32),
        _pipe@4 = write_bits_msb(_pipe@3, 0, 1),
        write_bits_msb(_pipe@4, Orig_ptr, 24)
    end,
    Writer@2 = emit_symbol_map(Writer@1, Unique),
    Writer@3 = write_bits_msb(Writer@2, 2, 3),
    Writer@4 = write_bits_msb(Writer@3, Num_groups, 15),
    Writer@5 = emit_selectors(Writer@4, Num_groups),
    Writer@6 = emit_two_tables(Writer@5, Lengths, Alphabet_size),
    emit_huffman_data(Writer@6, Symbols, Lengths, Codes).

-file("src/packkit/bzip2.gleam", 55).
?DOC(
    " Encode `bytes` as a bzip2 stream with an explicit block-size\n"
    " level (1..9).  The level only affects the stream header byte; the\n"
    " encoder always emits a single block so the level mostly carries\n"
    " over for round-trip tooling.\n"
).
-spec encode_with_level(bitstring(), integer()) -> {ok, bitstring()} |
    {error, packkit@error:codec_error()}.
encode_with_level(Bytes, Level) ->
    gleam@bool:guard(
        (Level < 1) orelse (Level > 9),
        {error, {codec_invalid_data, <<"bzip2 level must be in 1..9"/utf8>>}},
        fun() ->
            Level_byte = 16#30 + Level,
            Header = <<16#42, 16#5A, 16#68, Level_byte>>,
            case erlang:byte_size(Bytes) of
                0 ->
                    Writer = write_eos_marker(new_writer(), 0),
                    {ok,
                        gleam_stdlib:bit_array_concat(
                            [Header, flush_writer_msb(Writer)]
                        )};

                _ ->
                    Stream_crc = packkit@checksum:bzip2_crc32(Bytes),
                    Writer@1 = encode_block(new_writer(), Bytes),
                    Writer@2 = write_eos_marker(Writer@1, Stream_crc),
                    {ok,
                        gleam_stdlib:bit_array_concat(
                            [Header, flush_writer_msb(Writer@2)]
                        )}
            end
        end
    ).

-file("src/packkit/bzip2.gleam", 47).
?DOC(
    " Encode `bytes` as a bzip2 stream using the default block size 9\n"
    " (900 KiB).  The encoder emits a single block plus the stream\n"
    " trailer with the combined CRC; large inputs that exceed the\n"
    " block size will still be packed into one block, so a naive\n"
    " forward BWT may dominate the running time.\n"
).
-spec encode(bitstring()) -> {ok, bitstring()} |
    {error, packkit@error:codec_error()}.
encode(Bytes) ->
    encode_with_level(Bytes, 9).