package Moose::Meta::Method::Accessor::Native::Collection;

use strict;
use warnings;

our $VERSION = '1.24';
$VERSION = eval $VERSION;
our $AUTHORITY = 'cpan:STEVAN';

use Moose::Role;

requires qw( _adds_members );

around _inline_coerce_new_values => sub {
    shift;
    my $self = shift;

    return q{} unless $self->associated_attribute->should_coerce;

    return q{} unless $self->_tc_member_type_can_coerce;

    return
          '('
        . $self->_new_members
        . ') = map { $member_tc_obj->coerce($_) } '
        . $self->_new_members . ';';
};

sub _tc_member_type_can_coerce {
    my $self = shift;

    my $member_tc = $self->_tc_member_type;

    return $member_tc && $member_tc->has_coercion;
}

sub _tc_member_type {
    my $self = shift;

    for (
        my $tc = $self->associated_attribute->type_constraint;
        $tc;
        $tc = $tc->parent
        ) {

        return $tc->type_parameter
            if $tc->can('type_parameter');
    }

    return;
}

around _value_needs_copy => sub {
    shift;
    my $self = shift;

    return $self->_constraint_must_be_checked
        && !$self->_check_new_members_only;
};

around _inline_tc_code => sub {
    shift;
    my ( $self, $potential_value ) = @_;

    return q{} unless $self->_constraint_must_be_checked;

    if ( $self->_check_new_members_only ) {
        return q{} unless $self->_adds_members;

        return $self->_inline_check_member_constraint( $self->_new_members );
    }
    else {
        return $self->_inline_check_coercion($potential_value) . "\n"
            . $self->_inline_check_constraint($potential_value);
    }
};

sub _check_new_members_only {
    my $self = shift;

    my $attr = $self->associated_attribute;

    my $tc = $attr->type_constraint;

    # If we have a coercion, we could come up with an entirely new value after
    # coercing, so we need to check everything,
    return 0 if $attr->should_coerce && $tc->has_coercion;

    # If the parent is our root type (ArrayRef, HashRef, etc), that means we
    # can just check the new members of the collection, because we know that
    # we will always be generating an appropriate collection type.
    #
    # However, if this type has its own constraint (it's Parameteriz_able_,
    # not Paramet_erized_), we don't know what is being checked by the
    # constraint, so we need to check the whole value, not just the members.
    return 1
        if $self->_is_root_type( $tc->parent )
            && $tc->isa('Moose::Meta::TypeConstraint::Parameterized');

    return 0;
}

sub _inline_check_member_constraint {
    my ( $self, $new_value ) = @_;

    my $attr_name = $self->associated_attribute->name;

    return '$member_tc->($_) || '
        . $self->_inline_throw_error(
        qq{"A new member value for '$attr_name' does not pass its type constraint because: "}
            . ' . $member_tc_obj->get_message($_)',
        "data => \$_"
        ) . " for $new_value;";
}

around _inline_get_old_value_for_trigger => sub {
    shift;
    my ( $self, $instance ) = @_;

    my $attr = $self->associated_attribute;
    return '' unless $attr->has_trigger;

    return
          'my @old = '
        . $self->_inline_has($instance) . q{ ? }
        . $self->_inline_copy_old_value( $self->_inline_get($instance) )
        . ": ();\n";
};

around _eval_environment => sub {
    my $orig = shift;
    my $self = shift;

    my $env = $self->$orig(@_);

    my $member_tc = $self->_tc_member_type;

    return $env unless $member_tc;

    $env->{'$member_tc_obj'} = \($member_tc);

    $env->{'$member_tc'} = \( $member_tc->_compiled_type_constraint );

    return $env;
};

no Moose::Role;

1;
