package Catmandu::Store::ElasticSearch::Searcher;

use Catmandu::Sane;

our $VERSION = '1.0202';

use Moo;
use namespace::clean;

with 'Catmandu::Iterable';

has bag   => (is => 'ro', required => 1);
has query => (is => 'ro', required => 1);
has start => (is => 'ro', required => 1);
has limit => (is => 'ro', required => 1);
has total => (is => 'ro');
has sort  => (is => 'ro');

sub _paging_generator {
    my ($self) = @_;
    my $store  = $self->bag->store;
    my $es     = $store->es;
    my $id_key = $self->bag->id_key;
    my $index  = $self->bag->index;
    my $type   = $self->bag->type;
    my $query  = $self->query;
    my $sort   = $self->sort;

    sub {
        state $start = $self->start;
        state $total = $self->total;
        state $limit = $self->limit;
        state $hits;
        if (defined $total) {
            return unless $total;
        }
        unless ($hits && @$hits) {
            if ($total && $limit > $total) {
                $limit = $total;
            }
            my $body = {query => $query, from => $start, size => $limit,};
            $body->{sort} = $sort if defined $sort;
            my %args = (index => $index, body => $body,);
            if ($store->is_es_1_or_2) {
                $args{type} = $self->type;
            }
            my $res = $es->search(%args);

            $hits = $res->{hits}{hits};
            $start += $limit;
        }
        if ($total) {
            $total--;
        }
        my $doc  = shift(@$hits) || return;
        my $data = $doc->{_source};
        $data->{$id_key} = $doc->{_id};
        $data;
    };
}

sub generator {
    my ($self) = @_;

    # scroll + from isn't supported in es > 1.2
    if ($self->start) {
        return $self->_paging_generator;
    }

    my $bag    = $self->bag;
    my $store  = $bag->store;
    my $id_key = $bag->id_key;
    sub {
        state $total = $self->total;
        if (defined $total) {
            return unless $total;
        }

        state $scroll = do {
            my $body = {query => $self->query};
            $body->{sort} = $self->sort if $self->sort;
            my %args = (
                index => $bag->index,
                size  => $bag->buffer_size,  # TODO divide by number of shards
                body  => $body,
            );
            if (!$self->sort && $store->is_es_1_or_2) {
                $args{search_type} = 'scan';
            }
            if ($store->is_es_1_or_2) {
                $args{type} = $self->type;
            }
            $store->es->scroll_helper(%args);
        };

        my $doc = $scroll->next // do {
            $scroll->finish;
            return;
        };
        if ($total) {
            $total--;
        }
        my $data = $doc->{_source};
        $data->{$id_key} = $doc->{_id};
        $data;
    };
}

sub slice {    # TODO constrain total?
    my ($self, $start, $total) = @_;
    $start //= 0;
    $self->new(
        bag   => $self->bag,
        query => $self->query,
        start => $self->start + $start,
        limit => $self->limit,
        total => $total,
        sort  => $self->sort,
    );
}

sub count {
    my ($self) = @_;
    my $bag    = $self->bag;
    my $store  = $bag->store;
    my %args   = (index => $bag->index, body => {query => $self->query,},);
    if ($store->is_es_1_or_2) {
        $args{type} = $self->type;
    }
    $store->es->count(%args)->{count};
}

1;

__END__

=pod

=head1 NAME

Catmandu::Store::ElasticSearch::Bag - Searcher implementation for Elasticsearch

=head1 DESCRIPTION

This class isn't normally used directly. Instances are constructed using the store's C<searcher> method.

=head1 SEE ALSO

L<Catmandu::Iterable>

=cut