package Koha::REST::Plugin::Objects;

# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Koha is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <http://www.gnu.org/licenses>.

use Modern::Perl;

use Mojo::Base 'Mojolicious::Plugin';

use JSON;

=head1 NAME

Koha::REST::Plugin::Objects

=head1 API

=head2 Helper methods

=cut

sub register {
    my ( $self, $app ) = @_;

=head3 objects.find

    my $patrons_rs = Koha::Patrons->new;
    my $patrons = $c->objects->find( $patrons_rs, $id );
    . . .
    return $c->render({ status => 200, openapi => $patron });

Performs a database search using given Koha::Objects object and the $id.

Returns I<undef> if no object is found or the I<API representation> of
the requested object including any embeds specified in the request.

=cut

    $app->helper(
        'objects.find' => sub {
            my ( $c, $result_set, $id ) = @_;
            my $object = $c->objects->find_rs( $result_set, $id );
            return unless $object;
            return $c->objects->to_api($object);
        }
    );


=head3 objects.find_rs

    my $patrons_rs = Koha::Patrons->new;
    my $patron_rs = $c->objects->find_rs( $patrons_rs, $id );
    . . .
    return $c->render({ status => 200, openapi => $patron_rs->to_api });

Returns the passed Koha::Object result filtered to the passed $id and
with any embeds requested by the api applied.

The result object can then be used for further processing.

=cut

    $app->helper(
        'objects.find_rs' => sub {
            my ( $c, $result_set, $id ) = @_;

            my $attributes = {};

            # Generate prefetches for embedded stuff
            $c->dbic_merge_prefetch(
                {
                    attributes => $attributes,
                    result_set => $result_set
                }
            );

            my $object = $result_set->find( $id, $attributes );

            return $object;
        }
    );

=head3 objects.search

    my $patrons_rs = Koha::Patrons->new;
    my $patrons = $c->objects->search( $patrons_rs );
    . . .
    return $c->render({ status => 200, openapi => $patrons });

Performs a database search using given Koha::Objects object with any api
query parameters applied.

Returns an arrayref of I<API representations> of the requested objects
including any embeds specified in the request.

Warning: this helper adds pagination headers to the calling controller, and thus
shouldn't be called twice in it.

=cut

    $app->helper(
        'objects.search' => sub {
            my ( $c, $result_set ) = @_;

            # Generate the resultset using the HTTP request information
            my $objects_rs = $c->objects->search_rs($result_set);

            # Add pagination headers
            $c->add_pagination_headers();

            return $c->objects->to_api($objects_rs);
        }
    );

=head3 objects.search_rs

    my $patrons_object = Koha::Patrons->new;
    my $patrons_rs = $c->objects->search_rs( $patrons_object );
    . . .
    return $c->render({ status => 200, openapi => $patrons_rs->to_api });

Returns the passed Koha::Objects resultset filtered as requested by the api query
parameters and with requested embeds applied.

Warning: this helper stashes base values for the pagination headers to the calling
controller, and thus shouldn't be called twice in it.

=cut

    $app->helper(
        'objects.search_rs' => sub {
            my ( $c, $result_set ) = @_;

            my $args       = $c->validation->output;
            my $attributes = {};

            # Extract reserved params
            my ( $filtered_params, $reserved_params, $path_params ) =
              $c->extract_reserved_params($args);

            # Merge sorting into query attributes
            $c->dbic_merge_sorting(
                {
                    attributes => $attributes,
                    params     => $reserved_params,
                    result_set => $result_set
                }
            );

            # If no pagination parameters are passed, default
            $reserved_params->{_per_page} //=
              C4::Context->preference('RESTdefaultPageSize') // 20;
            $reserved_params->{_page} //= 1;

            $c->stash('koha.pagination.page'     => $reserved_params->{_page});
            $c->stash('koha.pagination.per_page' => $reserved_params->{_per_page});

            unless ( $reserved_params->{_per_page} == -1 ) {

                # Merge pagination into query attributes
                $c->dbic_merge_pagination(
                    {
                        filter => $attributes,
                        params => $reserved_params
                    }
                );
            }

            # Generate prefetches for embedded stuff
            $c->dbic_merge_prefetch(
                {
                    attributes => $attributes,
                    result_set => $result_set
                }
            );

            # Call the to_model function by reference, if defined
            if ( defined $filtered_params ) {

                # Apply the mapping function to the passed params
                $filtered_params =
                  $result_set->attributes_from_api($filtered_params);
                $filtered_params =
                  $c->build_query_params( $filtered_params, $reserved_params );
            }

            if (   defined $reserved_params->{q}
                || defined $reserved_params->{query}
                || defined $reserved_params->{'x-koha-query'} )
            {
                $filtered_params //= {};

                my @query_params_array;

                # query in request body, JSON::Validator already decoded it
                push @query_params_array, $reserved_params->{query}
                  if defined $reserved_params->{query};

                my $json = JSON->new;

                if ( ref( $reserved_params->{q} ) eq 'ARRAY' ) {

                   # q is defined as multi => JSON::Validator generates an array
                    foreach my $q ( @{ $reserved_params->{q} } ) {
                        push @query_params_array, $json->decode($q)
                          if $q;    # skip if exists but is empty
                    }
                }
                else {
                    # objects.search called outside OpenAPI context
                    # might be a hashref
                    push @query_params_array,
                      $json->decode( $reserved_params->{q} )
                      if $reserved_params->{q};
                }

                push @query_params_array,
                  $json->decode( $reserved_params->{'x-koha-query'} )
                  if defined $reserved_params->{'x-koha-query'};

                my $query_params;

                if ( scalar(@query_params_array) > 1 ) {
                    $query_params = { '-and' => \@query_params_array };
                }
                else {
                    $query_params = $query_params_array[0];
                }

                $filtered_params =
                  $c->merge_q_params( $filtered_params, $query_params,
                    $result_set );
            }

            # request sequence id (i.e. 'draw' Datatables parameter)
            $c->res->headers->add(
                'x-koha-request-id' => $reserved_params->{'x-koha-request-id'} )
              if $reserved_params->{'x-koha-request-id'};

            # If search_limited exists, use it
            $result_set = $result_set->search_limited,
              if $result_set->can('search_limited');

            $c->stash('koha.pagination.base_total'   => $result_set->count);
            $c->stash('koha.pagination.query_params' => $args);

            # Generate the resultset
            my $objects_rs = $result_set->search( $filtered_params, $attributes );
            # Stash the page total if requires, total otherwise
            $c->stash('koha.pagination.total' => $objects_rs->is_paged ? $objects_rs->pager->total_entries : $objects_rs->count);

            return $objects_rs;
        }
    );

=head3 objects.to_api

    my $patrons_rs = Koha::Patrons->new;
    my $api_representation = $c->objects->to_api( $patrons_rs );

Returns the API representation of the passed resultset.

=cut

    $app->helper(
        'objects.to_api' => sub {
            my ( $c, $object ) = @_;

            # Privileged request?
            my $public = $c->stash('is_public');

            # Look for embeds
            my $embed   = $c->stash('koha.embed');
            my $strings = $c->stash('koha.strings');

            return $object->to_api(
                {
                    embed   => $embed,
                    public  => $public,
                    strings => $strings
                }
            );
        }
    );
}

1;
