package Elastic::Model::SearchBuilder;
$Elastic::Model::SearchBuilder::VERSION = '0.29_2'; # TRIAL
use strict;
use warnings;
use ElasticSearch::SearchBuilder 0.18;
use parent 'ElasticSearch::SearchBuilder';
use Carp;

#===================================
sub _hashpair_ElasticDocREF {
#===================================
    my ( $self, $type, $k, $v ) = @_;
    $self->_uid_to_terms( $type, $k, $v->uid );
}

#===================================
sub _hashpair_ElasticUIDREF {
#===================================
    my ( $self, $type, $k, $v ) = @_;
    $self->_uid_to_terms( $type, $k, $v );
}

#===================================
sub _filter_field_terms {
#===================================
    my ( $self, $k, $op, $val ) = @_;

    return $self->_SWITCH_refkind(
        "Filter field operator -$op",
        $val,
        {   ElasticDocREF =>
                sub { $self->_uid_to_terms( 'filter', $k, $val->uid ) },
            ElasticUIDREF =>
                sub { $self->_uid_to_terms( 'filter', $k, $val ) },
            FALLBACK => sub {
                $self->SUPER::_filter_field_terms( $k, $op, $val );
            },
        }
    );
}

#===================================
sub _query_field_match {
#===================================
    my $self = shift;
    my ( $k, $op, $val ) = @_;

    return $self->_SWITCH_refkind(
        "Query field operator -$op",
        $val,
        {   ElasticDocREF =>
                sub { $self->_uid_to_terms( 'query', $k, $val->uid ) },
            ElasticUIDREF =>
                sub { $self->_uid_to_terms( 'query', $k, $val ) },
            FALLBACK => sub {
                $self->SUPER::_query_field_match( $k, $op, $val );
            },
        }
    );
}

#===================================
sub _uid_to_terms {
#===================================
    my ( $self, $type, $k, $uid ) = @_;
    my @clauses;
    for (qw(index type id)) {
        my $val = $uid->$_ or croak "UID missing ($_)";
        push @clauses, { term => { "${k}.uid.$_" => $val } };
    }
    return $type eq 'query'
        ? { bool => { must => \@clauses } }
        : { and => \@clauses }

}

#===================================
sub _refkind {
#===================================
    my ( $self, $data ) = @_;

    return 'UNDEF' unless defined $data;

    my $ref
        = !Scalar::Util::blessed($data)            ? ref $data
        : !$data->can('does')                      ? ''
        : $data->does('Elastic::Model::Role::Doc') ? 'ElasticDoc'
        : $data->isa('Elastic::Model::UID')        ? 'ElasticUID'
        :                                            '';

    return 'SCALAR' unless $ref;

    my $n_steps = 1;
    while ( $ref eq 'REF' ) {
        $data = $$data;
        $ref
            = !Scalar::Util::blessed($data)            ? ref $data
            : !$data->can('does')                      ? ''
            : $data->does('Elastic::Model::Role::Doc') ? 'ElasticDoc'
            : $data->isa('Elastic::Model::UID')        ? 'ElasticUID'
            :                                            '';
        $n_steps++ if $ref;
    }

    return ( $ref || 'SCALAR' ) . ( 'REF' x $n_steps );
}

1;

# ABSTRACT: An Elastic::Model specific subclass of L<ElasticSearch::SearchBuilder>

__END__

=pod

=encoding UTF-8

=head1 NAME

Elastic::Model::SearchBuilder - An Elastic::Model specific subclass of L<ElasticSearch::SearchBuilder>

=head1 VERSION

version 0.29_2

=head1 DESCRIPTION

L<Elastic::Model::SearchBuilder> is a sub-class of L<ElasticSearch::SearchBuilder>
to add automatic handling of L<Elastic::Doc> and L<Elastic::Model::UID>
values.

This document just explains the functionality that
L<Elastic::Model::SearchBuilder> adds.

B<For the full SearchBuilder docs, see L<ElasticSearch::SearchBuilder>>.

=head1 THE PROBLEM

Consider this class (where C<MyApp::User> is also an
L<Elastic::Doc> class):

    package MyApp::Comment;

    use Elastic::Doc;

    has 'user' => (
        is     => 'ro',
        isa    => 'MyApp::User,
    );

    has 'text' => (
        is     => 'ro',
        isa    => 'Str',
    );

We can create a comment as follows:

    $domain->create(
        comment => {
            text => 'I like Elastic::Model',
            user => $user,
        }
    );

The C<comment> object would be stored in Elasticsearch as something like this:

    {
        text    => "I like Elastic::Model",
        user    => {
            uid => {
                index   => 'myapp',
                type    => 'user',
                id      => 'abcdefg',
            },
            .... any other user fields....
        }
    }

In order to search for any comments by user C<$user>, you would need to do this:

    $view->type('comment')
         ->filterb(
                'user.uid.index' => $user->uid->index,
                'user.uid.type'  => $user->uid->type,
                'user.uid.id'    => $user->uid->id,
           )
         ->search;

=head1 THE SOLUTION

With L<Elastic::Model::SearchBuilder>, you can do it as follows:

    $view->type('comment')
         ->filterb( user => $user )
         ->search;

Or with the C<UID>:

    $view->type('comment')
         ->filterb( user => $user->uid )
         ->search;

=head1 FURTHER EXAMPLES

=head2 Query or Filter

This works for both queries and filters, eg:

    $view->queryb ( user => $user )->search;
    $view->filterb( user => $user )->search;

=head2 Doc or UID

You can use either the doc/object itself, or an L<Elastic::Model::UID> object:

    $uid = $user->uid;
    $view->queryb ( user => $uid )->search;
    $view->filterb( user => $uid )->search;

=head2 Negating queries:

    $view->queryb ( user => { '!=' => $user })->search;
    $view->filterb( user => { '!=' => $user })->search;

=head2 "IN" queries

    $view->queryb ( user => \@users )->search;
    $view->filterb( user => \@users )->search;

=head2 "NOT IN" queries

    $view->queryb ( user => { '!=' => \@users })->search;
    $view->filterb( user => { '!=' => \@users })->search;

=head1 AUTHOR

Clinton Gormley <drtech@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2014 by Clinton Gormley.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
