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

-export([register/2, new_query/1, with_values/2, execute/2, string/1, decode/2, all/3, one/3, int/1, float/1, bool/1, null/0]).
-export_type([value/0, based_error/0, 'query'/0, based_adapter/3, returned/1, db/0, message/0]).

-type value() :: {string, binary()} |
    {int, integer()} |
    {float, float()} |
    {bool, boolean()} |
    null.

-type based_error() :: {based_error, binary(), binary(), binary()}.

-type 'query'() :: {'query', binary(), list(value())}.

-type based_adapter(HFB, HFC, HFD) :: {based_adapter,
        fun((HFB, fun((HFC) -> HFD)) -> HFD),
        HFB,
        fun(('query'(), HFC) -> {ok, list(gleam@dynamic:dynamic_())} |
            {error, based_error()})}.

-type returned(HFE) :: {returned, integer(), list(HFE)}.

-opaque db() :: {db, gleam@erlang@process:subject(message())}.

-type message() :: {execute,
        gleam@erlang@process:subject({ok, list(gleam@dynamic:dynamic_())} |
            {error, based_error()}),
        'query'()} |
    shutdown.

-spec shutdown(gleam@erlang@process:subject(message())) -> nil.
shutdown(Actor) ->
    gleam@erlang@process:send(Actor, shutdown).

-spec handle_message(
    message(),
    {HFY,
        fun(('query'(), HFY) -> {ok, list(gleam@dynamic:dynamic_())} |
            {error, based_error()})}
) -> gleam@otp@actor:next(message(), {HFY,
    fun(('query'(), HFY) -> {ok, list(gleam@dynamic:dynamic_())} |
        {error, based_error()})}).
handle_message(Message, Backend) ->
    case Message of
        shutdown ->
            {stop, normal};

        {execute, Client, Query} ->
            {Conn, Service} = Backend,
            gleam@erlang@process:send(Client, Service(Query, Conn)),
            gleam@otp@actor:continue(Backend)
    end.

-spec start(
    HFS,
    fun(('query'(), HFS) -> {ok, list(gleam@dynamic:dynamic_())} |
        {error, based_error()})
) -> {ok, gleam@erlang@process:subject(message())} |
    {error, gleam@otp@actor:start_error()}.
start(Conn, Service) ->
    gleam@otp@actor:start({Conn, Service}, fun handle_message/2).

-spec register(based_adapter(any(), any(), HFO), fun((db()) -> HFO)) -> HFO.
register(Based_adapter, Callback) ->
    {based_adapter, With_connection, Conf, Service} = Based_adapter,
    With_connection(
        Conf,
        fun(Connection) ->
            _assert_subject = start(Connection, Service),
            {ok, Actor} = case _assert_subject of
                {ok, _} -> _assert_subject;
                _assert_fail ->
                    erlang:error(#{gleam_error => let_assert,
                                message => <<"Assertion pattern match failed"/utf8>>,
                                value => _assert_fail,
                                module => <<"based"/utf8>>,
                                function => <<"register"/utf8>>,
                                line => 68})
            end,
            Result = Callback({db, Actor}),
            shutdown(Actor),
            Result
        end
    ).

-spec new_query(binary()) -> 'query'().
new_query(Sql) ->
    {'query', Sql, []}.

-spec with_values('query'(), list(value())) -> 'query'().
with_values(Query, Values) ->
    {'query',
        erlang:element(2, Query),
        lists:append(erlang:element(3, Query), Values)}.

-spec execute('query'(), db()) -> {ok, list(gleam@dynamic:dynamic_())} |
    {error, based_error()}.
execute(Query, Db) ->
    {db, Subject} = Db,
    gleam@erlang@process:call(
        Subject,
        fun(_capture) -> {execute, _capture, Query} end,
        5000
    ).

-spec string(binary()) -> value().
string(Value) ->
    {string, Value}.

-spec decode_error(list(gleam@dynamic:decode_error())) -> based_error().
decode_error(Errors) ->
    [{decode_error, Expected, Actual, Path} | _] = case Errors of
        [{decode_error, _, _, _} | _] -> Errors;
        _assert_fail ->
            erlang:error(#{gleam_error => let_assert,
                        message => <<"Assertion pattern match failed"/utf8>>,
                        value => _assert_fail,
                        module => <<"based"/utf8>>,
                        function => <<"decode_error"/utf8>>,
                        line => 171})
    end,
    Path@1 = gleam@string:join(Path, <<"."/utf8>>),
    Message = <<<<<<<<<<"Decoder failed, expected "/utf8, Expected/binary>>/binary,
                    ", got "/utf8>>/binary,
                Actual/binary>>/binary,
            " in "/utf8>>/binary,
        Path@1/binary>>,
    {based_error, <<""/utf8>>, <<"decode_error"/utf8>>, Message}.

-spec decode(
    list(gleam@dynamic:dynamic_()),
    fun((gleam@dynamic:dynamic_()) -> {ok, HGR} |
        {error, list(gleam@dynamic:decode_error())})
) -> {ok, returned(HGR)} | {error, based_error()}.
decode(Rows, Decoder) ->
    gleam@result:'try'(
        begin
            _pipe = gleam@list:try_map(Rows, Decoder),
            gleam@result:map_error(_pipe, fun decode_error/1)
        end,
        fun(Rows@1) -> _pipe@1 = erlang:length(Rows@1),
            _pipe@2 = {returned, _pipe@1, Rows@1},
            {ok, _pipe@2} end
    ).

-spec all(
    'query'(),
    db(),
    fun((gleam@dynamic:dynamic_()) -> {ok, HGE} |
        {error, list(gleam@dynamic:decode_error())})
) -> {ok, returned(HGE)} | {error, based_error()}.
all(Query, Db, Decoder) ->
    gleam@result:'try'(
        execute(Query, Db),
        fun(Rows) -> decode(Rows, Decoder) end
    ).

-spec one(
    'query'(),
    db(),
    fun((gleam@dynamic:dynamic_()) -> {ok, HGJ} |
        {error, list(gleam@dynamic:decode_error())})
) -> {ok, HGJ} | {error, based_error()}.
one(Query, Db, Decoder) ->
    gleam@result:'try'(
        execute(Query, Db),
        fun(Rows) ->
            Returned = decode(Rows, Decoder),
            gleam@result:'try'(
                Returned,
                fun(Returned@1) ->
                    {returned, _, Rows@1} = Returned@1,
                    gleam@result:'try'(
                        begin
                            _pipe = Rows@1,
                            _pipe@1 = gleam@list:first(_pipe),
                            gleam@result:replace_error(
                                _pipe@1,
                                {based_error,
                                    <<""/utf8>>,
                                    <<"not_found"/utf8>>,
                                    <<"Expected one row but found none"/utf8>>}
                            )
                        end,
                        fun(Row) -> {ok, Row} end
                    )
                end
            )
        end
    ).

-spec int(integer()) -> value().
int(Value) ->
    {int, Value}.

-spec float(float()) -> value().
float(Value) ->
    {float, Value}.

-spec bool(boolean()) -> value().
bool(Value) ->
    {bool, Value}.

-spec null() -> value().
null() ->
    null.
