-module(pprint).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).

-export([with_config/2, debug/1, format/1, styled/1]).
-export_type([config/0, style_mode/0, bit_array_mode/0, label_mode/0]).

-type config() :: {config, style_mode(), bit_array_mode(), label_mode()}.

-type style_mode() :: styled | unstyled.

-type bit_array_mode() :: bit_arrays_as_string | keep_bit_arrays.

-type label_mode() :: labels | no_labels.

-spec wrap(
    glam@doc:document(),
    glam@doc:document(),
    glam@doc:document(),
    binary()
) -> glam@doc:document().
wrap(Document, Open, Close, Trailing) ->
    _pipe = Document,
    _pipe@1 = glam@doc:prepend_docs(
        _pipe,
        [Open, {break, <<""/utf8>>, <<""/utf8>>}]
    ),
    _pipe@2 = glam@doc:nest(_pipe@1, 2),
    _pipe@3 = glam@doc:append_docs(
        _pipe@2,
        [glam@doc:break(<<""/utf8>>, Trailing), Close]
    ),
    glam@doc:group(_pipe@3).

-spec ansi(binary(), binary(), config()) -> glam@doc:document().
ansi(Text, Code, Config) ->
    Text_doc = glam@doc:from_string(Text),
    case erlang:element(2, Config) of
        unstyled ->
            Text_doc;

        styled ->
            glam@doc:concat(
                [glam@doc:zero_width_string(Code),
                    Text_doc,
                    glam@doc:zero_width_string(<<"\x{001b}[0m"/utf8>>)]
            )
    end.

-spec pretty_string(binary(), config()) -> glam@doc:document().
pretty_string(String, Config) ->
    _pipe = (<<<<"\""/utf8, String/binary>>/binary, "\""/utf8>>),
    ansi(_pipe, <<"\x{001b}[38;5;2m"/utf8>>, Config).

-spec pretty_bit_array(bitstring(), config()) -> glam@doc:document().
pretty_bit_array(Bits, Config) ->
    _pipe = gleam@string:inspect(Bits),
    ansi(_pipe, <<"\x{001b}[38;5;5m"/utf8>>, Config).

-spec pretty_tuple(list(gleam@dynamic:dynamic_()), config()) -> glam@doc:document().
pretty_tuple(Items, Config) ->
    _pipe = gleam@list:map(
        Items,
        fun(_capture) -> pretty_dynamic(_capture, Config) end
    ),
    _pipe@1 = glam@doc:concat_join(
        _pipe,
        [glam@doc:from_string(<<","/utf8>>), {break, <<" "/utf8>>, <<""/utf8>>}]
    ),
    wrap(
        _pipe@1,
        glam@doc:from_string(<<"#("/utf8>>),
        glam@doc:from_string(<<")"/utf8>>),
        <<","/utf8>>
    ).

-spec pretty_dynamic(gleam@dynamic:dynamic_(), config()) -> glam@doc:document().
pretty_dynamic(Value, Config) ->
    _pipe = Value,
    _pipe@1 = pprint@decoder:classify(_pipe),
    pretty_type(_pipe@1, Config).

-spec pretty_type(pprint@decoder:type(), config()) -> glam@doc:document().
pretty_type(Value, Config) ->
    case Value of
        {t_string, S} ->
            pretty_string(S, Config);

        {t_int, I} ->
            _pipe = gleam@int:to_string(I),
            ansi(_pipe, <<"\x{001b}[38;5;3m"/utf8>>, Config);

        {t_float, F} ->
            _pipe@1 = gleam@float:to_string(F),
            ansi(_pipe@1, <<"\x{001b}[38;5;3m"/utf8>>, Config);

        {t_bool, B} ->
            _pipe@2 = gleam@bool:to_string(B),
            ansi(_pipe@2, <<"\x{001b}[38;5;4m"/utf8>>, Config);

        {t_bit_array, B@1} ->
            case erlang:element(3, Config) of
                keep_bit_arrays ->
                    pretty_bit_array(B@1, Config);

                bit_arrays_as_string ->
                    case gleam@bit_array:to_string(B@1) of
                        {ok, S@1} ->
                            pretty_string(S@1, Config);

                        {error, nil} ->
                            pretty_bit_array(B@1, Config)
                    end
            end;

        t_nil ->
            ansi(<<"Nil"/utf8>>, <<"\x{001b}[38;5;4m"/utf8>>, Config);

        {t_list, Items} ->
            pretty_list(Items, Config);

        {t_dict, D} ->
            pretty_dict(D, Config);

        {t_tuple, Items@1} ->
            pretty_tuple(Items@1, Config);

        {t_custom, Name, Fields} ->
            pretty_custom_type(Name, Fields, Config);

        {t_foreign, F@1} ->
            ansi(F@1, <<"\x{001b}[2m"/utf8>>, Config)
    end.

-spec with_config(any(), config()) -> binary().
with_config(Value, Config) ->
    _pipe = Value,
    _pipe@1 = gleam@dynamic:from(_pipe),
    _pipe@2 = pretty_dynamic(_pipe@1, Config),
    glam@doc:to_string(_pipe@2, 40).

-spec debug(MEZ) -> MEZ.
debug(Value) ->
    _pipe = Value,
    _pipe@1 = with_config(_pipe, {config, styled, keep_bit_arrays, labels}),
    gleam@io:println_error(_pipe@1),
    Value.

-spec format(any()) -> binary().
format(Value) ->
    with_config(Value, {config, unstyled, bit_arrays_as_string, no_labels}).

-spec styled(any()) -> binary().
styled(Value) ->
    with_config(Value, {config, styled, bit_arrays_as_string, no_labels}).

-spec pretty_list(list(gleam@dynamic:dynamic_()), config()) -> glam@doc:document().
pretty_list(Items, Config) ->
    Items@1 = gleam@list:map(Items, fun pprint@decoder:classify/1),
    Space = case Items@1 of
        [{t_int, _} | _] ->
            {flex_break, <<" "/utf8>>, <<""/utf8>>};

        [{t_float, _} | _] ->
            {flex_break, <<" "/utf8>>, <<""/utf8>>};

        _ ->
            {break, <<" "/utf8>>, <<""/utf8>>}
    end,
    _pipe = gleam@list:map(
        Items@1,
        fun(_capture) -> pretty_type(_capture, Config) end
    ),
    _pipe@1 = glam@doc:concat_join(
        _pipe,
        [glam@doc:from_string(<<","/utf8>>), Space]
    ),
    wrap(
        _pipe@1,
        glam@doc:from_string(<<"["/utf8>>),
        glam@doc:from_string(<<"]"/utf8>>),
        <<","/utf8>>
    ).

-spec pretty_dict(
    gleam@dict:dict(pprint@decoder:type(), pprint@decoder:type()),
    config()
) -> glam@doc:document().
pretty_dict(D, Config) ->
    _pipe = maps:to_list(D),
    _pipe@1 = gleam@list:sort(
        _pipe,
        fun(One_field, Other_field) ->
            {One_key, _} = One_field,
            {Other_key, _} = Other_field,
            gleam@string:compare(
                gleam@string:inspect(One_key),
                gleam@string:inspect(Other_key)
            )
        end
    ),
    _pipe@3 = gleam@list:map(
        _pipe@1,
        fun(Field) ->
            _pipe@2 = [glam@doc:from_string(<<"#("/utf8>>),
                pretty_type(erlang:element(1, Field), Config),
                glam@doc:from_string(<<", "/utf8>>),
                pretty_type(erlang:element(2, Field), Config),
                glam@doc:from_string(<<")"/utf8>>)],
            glam@doc:concat(_pipe@2)
        end
    ),
    _pipe@4 = glam@doc:concat_join(
        _pipe@3,
        [glam@doc:from_string(<<","/utf8>>), {break, <<" "/utf8>>, <<""/utf8>>}]
    ),
    wrap(
        _pipe@4,
        glam@doc:from_string(<<"dict.from_list(["/utf8>>),
        glam@doc:from_string(<<"])"/utf8>>),
        <<","/utf8>>
    ).

-spec pretty_custom_type(binary(), list(pprint@decoder:field()), config()) -> glam@doc:document().
pretty_custom_type(Name, Fields, Config) ->
    Style = case Name of
        <<"Ok"/utf8>> ->
            <<"\x{001b}[1m"/utf8>>;

        <<"Error"/utf8>> ->
            <<"\x{001b}[1m"/utf8>>;

        <<"Some"/utf8>> ->
            <<"\x{001b}[1m"/utf8>>;

        <<"None"/utf8>> ->
            <<"\x{001b}[1m"/utf8>>;

        _ ->
            <<""/utf8>>
    end,
    Fields@1 = gleam@list:map(
        Fields,
        fun(Field) -> case {Field, erlang:element(4, Config)} of
                {{positional, Value}, labels} ->
                    pretty_dynamic(Value, Config);

                {{positional, Value}, no_labels} ->
                    pretty_dynamic(Value, Config);

                {{labelled, _, Value}, no_labels} ->
                    pretty_dynamic(Value, Config);

                {{labelled, Label, Value@1}, labels} ->
                    glam@doc:concat(
                        [ansi(
                                <<Label/binary, ": "/utf8>>,
                                <<"\x{001b}[2m"/utf8>>,
                                Config
                            ),
                            pretty_dynamic(Value@1, Config)]
                    )
            end end
    ),
    Name@1 = ansi(Name, Style, Config),
    Open = glam@doc:concat([Name@1, glam@doc:from_string(<<"("/utf8>>)]),
    Close = glam@doc:from_string(<<")"/utf8>>),
    case Fields@1 of
        [] ->
            Name@1;

        [Single] ->
            glam@doc:concat([Open, Single, Close]);

        _ ->
            _pipe = Fields@1,
            _pipe@1 = glam@doc:concat_join(
                _pipe,
                [glam@doc:from_string(<<","/utf8>>),
                    {break, <<" "/utf8>>, <<""/utf8>>}]
            ),
            wrap(_pipe@1, Open, Close, <<","/utf8>>)
    end.
