-module(gramps@http).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).

-export([read_response/1, read_request/1, status_to_bit_array/1, encode_headers/1, response_builder/2, to_bytes_builder/1]).
-export_type([packet_type/0, decode_packet_error/0, uri_packet/0, decoded_packet/0]).

-type packet_type() :: httph_bin | http_bin.

-type decode_packet_error() :: {more, integer()} | {http_error, binary()}.

-type uri_packet() :: {abs_path, binary()} |
    {absolute_uri, gleam@http:scheme(), binary(), integer(), binary()}.

-type decoded_packet() :: {http_request,
        binary(),
        uri_packet(),
        {integer(), integer()}} |
    {http_response, {integer(), integer()}, integer(), binary()} |
    {http_header, integer(), gleam@dynamic:dynamic_(), binary(), binary()} |
    http_eoh.

-spec get_headers(bitstring(), list({binary(), binary()})) -> {ok,
        {list({binary(), binary()}), bitstring()}} |
    {error, decode_packet_error()}.
get_headers(Data, Headers) ->
    case gramps_ffi:decode_packet(httph_bin, Data, []) of
        {ok, {http_eoh, Rest}} ->
            {ok, {Headers, Rest}};

        {ok, {{http_header, _, _, Field, Value}, Rest@1}} ->
            get_headers(
                Rest@1,
                [{gleam@string:lowercase(Field), Value} | Headers]
            );

        {ok, Val} ->
            {error,
                {http_error,
                    <<"Expected only headers but got request/response: "/utf8,
                        (gleam@string:inspect(Val))/binary>>}};

        {error, Reason} ->
            {error, Reason}
    end.

-spec read_response(bitstring()) -> {ok,
        {gleam@http@response:response(nil), bitstring()}} |
    {error, decode_packet_error()}.
read_response(Data) ->
    case gramps_ffi:decode_packet(http_bin, Data, []) of
        {ok, {{http_response, _, Status, _}, Rest}} ->
            case get_headers(Rest, []) of
                {ok, {Headers, Rest@1}} ->
                    {ok, {{response, Status, Headers, nil}, Rest@1}};

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

        {error, Reason@1} ->
            {error, Reason@1};

        _ ->
            {error, {http_error, <<"Unexpected data"/utf8>>}}
    end.

-spec read_request(bitstring()) -> {ok,
        {gleam@http@request:request(nil), bitstring()}} |
    {error, decode_packet_error()}.
read_request(Data) ->
    case gramps_ffi:decode_packet(http_bin, Data, []) of
        {ok, {{http_request, Method, Uri, _}, Rest}} ->
            case get_headers(Rest, []) of
                {ok, {Headers, Rest@1}} ->
                    Host = begin
                        _pipe = Headers,
                        _pipe@1 = gleam@list:key_find(_pipe, <<"host"/utf8>>),
                        gleam@result:unwrap(_pipe@1, <<""/utf8>>)
                    end,
                    {Scheme@1, Host@2, Port@1, Path@2} = case Uri of
                        {absolute_uri, Scheme, Host@1, Port, Path} ->
                            {Scheme, Host@1, {some, Port}, Path};

                        {abs_path, Path@1} ->
                            {http, Host, none, Path@1}
                    end,
                    {Path@3, Query} = begin
                        _pipe@2 = Path@2,
                        _pipe@3 = gleam@string:split_once(_pipe@2, <<"?"/utf8>>),
                        _pipe@4 = gleam@result:map(
                            _pipe@3,
                            fun(Pair) ->
                                {erlang:element(1, Pair),
                                    {some, erlang:element(2, Pair)}}
                            end
                        ),
                        gleam@result:unwrap(_pipe@4, {Path@2, none})
                    end,
                    Method@1 = gleam@http:method_from_dynamic(
                        gleam@dynamic:from(Method)
                    ),
                    {ok,
                        {{request,
                                gleam@result:unwrap(Method@1, get),
                                Headers,
                                nil,
                                Scheme@1,
                                Host@2,
                                Port@1,
                                Path@3,
                                Query},
                            Rest@1}};

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

        {error, Reason@1} ->
            {error, Reason@1};

        Err ->
            {error,
                {http_error,
                    <<"Unexpected data: "/utf8,
                        (gleam@string:inspect(Err))/binary>>}}
    end.

-spec status_to_bit_array(integer()) -> bitstring().
status_to_bit_array(Status) ->
    case Status of
        100 ->
            <<"Continue"/utf8>>;

        101 ->
            <<"Switching Protocols"/utf8>>;

        103 ->
            <<"Early Hints"/utf8>>;

        200 ->
            <<"OK"/utf8>>;

        201 ->
            <<"Created"/utf8>>;

        202 ->
            <<"Accepted"/utf8>>;

        203 ->
            <<"Non-Authoritative Information"/utf8>>;

        204 ->
            <<"No Content"/utf8>>;

        205 ->
            <<"Reset Content"/utf8>>;

        206 ->
            <<"Partial Content"/utf8>>;

        300 ->
            <<"Multiple Choices"/utf8>>;

        301 ->
            <<"Moved Permanently"/utf8>>;

        302 ->
            <<"Found"/utf8>>;

        303 ->
            <<"See Other"/utf8>>;

        304 ->
            <<"Not Modified"/utf8>>;

        307 ->
            <<"Temporary Redirect"/utf8>>;

        308 ->
            <<"Permanent Redirect"/utf8>>;

        400 ->
            <<"Bad Request"/utf8>>;

        401 ->
            <<"Unauthorized"/utf8>>;

        402 ->
            <<"Payment Required"/utf8>>;

        403 ->
            <<"Forbidden"/utf8>>;

        404 ->
            <<"Not Found"/utf8>>;

        405 ->
            <<"Method Not Allowed"/utf8>>;

        406 ->
            <<"Not Acceptable"/utf8>>;

        407 ->
            <<"Proxy Authentication Required"/utf8>>;

        408 ->
            <<"Request Timeout"/utf8>>;

        409 ->
            <<"Conflict"/utf8>>;

        410 ->
            <<"Gone"/utf8>>;

        411 ->
            <<"Length Required"/utf8>>;

        412 ->
            <<"Precondition Failed"/utf8>>;

        413 ->
            <<"Payload Too Large"/utf8>>;

        414 ->
            <<"URI Too Long"/utf8>>;

        415 ->
            <<"Unsupported Media Type"/utf8>>;

        416 ->
            <<"Range Not Satisfiable"/utf8>>;

        417 ->
            <<"Expectation Failed"/utf8>>;

        418 ->
            <<"I'm a teapot"/utf8>>;

        422 ->
            <<"Unprocessable Entity"/utf8>>;

        425 ->
            <<"Too Early"/utf8>>;

        426 ->
            <<"Upgrade Required"/utf8>>;

        428 ->
            <<"Precondition Required"/utf8>>;

        429 ->
            <<"Too Many Requests"/utf8>>;

        431 ->
            <<"Request Header Fields Too Large"/utf8>>;

        451 ->
            <<"Unavailable For Legal Reasons"/utf8>>;

        500 ->
            <<"Internal Server Error"/utf8>>;

        501 ->
            <<"Not Implemented"/utf8>>;

        502 ->
            <<"Bad Gateway"/utf8>>;

        503 ->
            <<"Service Unavailable"/utf8>>;

        504 ->
            <<"Gateway Timeout"/utf8>>;

        505 ->
            <<"HTTP Version Not Supported"/utf8>>;

        506 ->
            <<"Variant Also Negotiates"/utf8>>;

        507 ->
            <<"Insufficient Storage"/utf8>>;

        508 ->
            <<"Loop Detected"/utf8>>;

        510 ->
            <<"Not Extended"/utf8>>;

        511 ->
            <<"Network Authentication Required"/utf8>>;

        _ ->
            <<"Unknown HTTP Status"/utf8>>
    end.

-spec encode_headers(list({binary(), binary()})) -> gleam@bytes_builder:bytes_builder().
encode_headers(Headers) ->
    gleam@list:fold(
        Headers,
        gleam@bytes_builder:new(),
        fun(Builder, Tup) ->
            {Header, Value} = Tup,
            _pipe = Builder,
            _pipe@1 = gleam@bytes_builder:append_string(_pipe, Header),
            _pipe@2 = gleam@bytes_builder:append(_pipe@1, <<": "/utf8>>),
            _pipe@3 = gleam@bytes_builder:append_string(_pipe@2, Value),
            gleam@bytes_builder:append(_pipe@3, <<"\r\n"/utf8>>)
        end
    ).

-spec response_builder(integer(), list({binary(), binary()})) -> gleam@bytes_builder:bytes_builder().
response_builder(Status, Headers) ->
    Status_string = begin
        _pipe = Status,
        _pipe@1 = gleam@int:to_string(_pipe),
        _pipe@2 = gleam_stdlib:wrap_list(_pipe@1),
        _pipe@3 = gleam@bytes_builder:append(_pipe@2, <<" "/utf8>>),
        gleam@bytes_builder:append(_pipe@3, status_to_bit_array(Status))
    end,
    _pipe@4 = gleam@bytes_builder:new(),
    _pipe@5 = gleam@bytes_builder:append(_pipe@4, <<"HTTP/1.1 "/utf8>>),
    _pipe@6 = gleam_stdlib:iodata_append(_pipe@5, Status_string),
    _pipe@7 = gleam@bytes_builder:append(_pipe@6, <<"\r\n"/utf8>>),
    _pipe@8 = gleam_stdlib:iodata_append(_pipe@7, encode_headers(Headers)),
    gleam@bytes_builder:append(_pipe@8, <<"\r\n"/utf8>>).

-spec to_bytes_builder(
    gleam@http@response:response(gleam@bytes_builder:bytes_builder())
) -> gleam@bytes_builder:bytes_builder().
to_bytes_builder(Resp) ->
    _pipe = erlang:element(2, Resp),
    _pipe@1 = response_builder(_pipe, erlang:element(3, Resp)),
    gleam_stdlib:iodata_append(_pipe@1, erlang:element(4, Resp)).
