use strict;
use warnings;

use Test2::Bundle::Extended;
use Test2::Plugin::NoWarnings;

use Params::ValidationCompiler qw( validation_for );
use Specio::Declare;
use Specio::Library::Builtins;

subtest(
    'type can be inlined',
    sub {
        _test_int_type( t('Int') );
    }
);

declare(
    'MyInt',
    where => sub { $_[0] =~ /\A-?[0-9]+\z/ },
);

declare(
    'MyInt2',
    where => sub { $_[0] =~ /\A-?[0-9]+\z/ },
);

declare(
    'ArrayRefOfInt',
    parent => t( 'ArrayRef', of => t('Int') ),
);

declare(
    'ArrayRefOfInt2',
    parent => t( 'ArrayRef', of => t('Int') ),
);

subtest(
    'type cannot be inlined',
    sub {
        _test_int_type( t('MyInt') );
    }
);

subtest(
    'type and coercion can be inlined',
    sub {
        coerce(
            t('ArrayRefOfInt'),
            from   => t('Int'),
            inline => sub { return "[ $_[1] ]" },
        );

        _test_arrayref_to_int_coercion( t('ArrayRefOfInt') );
    }
);

subtest(
    'type can be inlined but coercion cannot',
    sub {
        coerce(
            t('ArrayRefOfInt2'),
            from  => t('Int'),
            using => sub { return [ $_[0] ] },
        );

        _test_arrayref_to_int_coercion( t('ArrayRefOfInt2') );
    }
);

subtest(
    'type cannot be inlined but coercion can',
    sub {
        coerce(
            t('MyInt'),
            from   => t('ArrayRef'),
            inline => sub { return "scalar \@{ $_[1] }" },
        );

        _test_arrayref_to_int_coercion( t('MyInt') );
    }
);

subtest(
    'neither type not coercion can be inlined',
    sub {
        coerce(
            t('MyInt2'),
            from  => t('ArrayRef'),
            using => sub { return scalar @{ $_[0] } },
        );

        _test_arrayref_to_int_coercion( t('MyInt2') );
    }
);

done_testing();

sub _test_int_type {
    my $type = shift;

    my $sub = validation_for(
        params => {
            foo => { type => $type },
        },
    );

    is(
        dies { $sub->( foo => 42 ) },
        undef,
        'lives when foo is an integer'
    );

    my $name = $type->name;
    like(
        dies { $sub->( foo => [] ) },
        qr/Validation failed for type named $name declared in .+ with value \Q[  ]/,
        'dies when foo is an arrayref'
    );
}

sub _test_arrayref_to_int_coercion {
    my $type = shift;

    my $sub = validation_for(
        params => {
            foo => { type => $type },
        },
    );

    is(
        dies { $sub->( foo => 42 ) },
        undef,
        'lives when foo is an integer'
    );

    is(
        dies { $sub->( foo => [ 42, 1 ] ) },
        undef,
        'lives when foo is an arrayref of integers'
    );

    my $name = $type->name;
    like(
        dies { $sub->( foo => {} ) },
        qr/Validation failed for type named $name declared in .+ with value \Q{  }/,
        'dies when foo is a hashref'
    );
}
