#!/usr/bin/env perl

use strict;
use warnings;
use Test2::V0;
use Params::Filter qw/filter/;

# Functional Interface Tests

subtest 'Basic functional interface - hashref input' => sub {
    my ($result, $msg) = filter(
        { name => 'Alice', email => 'alice@example.com', phone => '555-1234' },
        ['name', 'email'],
        ['phone'],
    );

    ok $result, 'Validation succeeds';
    is $result->{name}, 'Alice', 'Required field: name';
    is $result->{email}, 'alice@example.com', 'Required field: email';
    is $result->{phone}, '555-1234', 'Accepted field: phone';
    like $msg, qr/Admitted/, 'Status message indicates success';
};

subtest 'Missing required field' => sub {
    my ($result, $msg) = filter(
        { name => 'Bob' },  # email missing
        ['name', 'email'],
        ['phone'],
    );

    ok !$result, 'Validation fails when required field missing';
    like $msg, qr/Unable to initialize without (all )?required arguments/, 'Error message mentions missing required';
    like $msg, qr/'email'/, 'Error message lists missing field';
};

subtest 'Only required fields, no accepted' => sub {
    my ($result, $msg) = filter(
        { name => 'Charlie', email => 'charlie@example.com', extra => 'ignored' },
        ['name', 'email'],
        [],  # No accepted fields
    );

    ok $result, 'Validation succeeds';
    is $result->{name}, 'Charlie', 'Required field present';
    is $result->{email}, 'charlie@example.com', 'Required field present';
    ok !exists $result->{extra}, 'Extra field not included';
};

subtest 'Wildcard acceptance' => sub {
    my ($result, $msg) = filter(
        { id => 1, name => 'Test', value => 42, active => 1 },
        ['id'],
        ['*'],  # Wildcard
    );

    ok $result, 'Validation succeeds';
    is $result->{id}, 1, 'Required field';
    is $result->{name}, 'Test', 'Wildcard accepted name';
    is $result->{value}, 42, 'Wildcard accepted value';
    is $result->{active}, 1, 'Wildcard accepted active';
};

subtest 'Excluded fields' => sub {
    my ($result, $msg) = filter(
        {
            name     => 'Diana',
            email    => 'diana@example.com',
            password => 'secret123',
            ssn      => '123-45-6789',
        },
        ['name'],
        ['email'],
        ['password', 'ssn'],
    );

    ok $result, 'Validation succeeds';
    is $result->{name}, 'Diana', 'Required field present';
    is $result->{email}, 'diana@example.com', 'Accepted field present';
    ok !exists $result->{password}, 'Excluded field removed: password';
    ok !exists $result->{ssn}, 'Excluded field removed: ssn';
};

subtest 'Arrayref input - even elements' => sub {
    my ($result, $msg) = filter(
        ['name', 'Eve', 'email', 'eve@example.com', 'age', 30],
        ['name', 'email'],
        ['age'],
    );

    ok $result, 'Validation succeeds';
    is $result->{name}, 'Eve', 'Field from array: name';
    is $result->{email}, 'eve@example.com', 'Field from array: email';
    is $result->{age}, 30, 'Field from array: age';
};

subtest 'Arrayref input - odd elements (becomes flag)' => sub {
    my ($result, $msg) = filter(
        ['name', 'Frank', 'verbose'],
        ['name'],
        ['verbose'],
        [],  # No excluded
        1,  # Debug on
    );

    ok $result, 'Validation succeeds';
    is $result->{name}, 'Frank', 'Required field';
    is $result->{verbose}, 1, 'Odd element becomes flag with value 1';
    like $msg, qr/Odd number of arguments/, 'Debug warns about odd elements';
};

subtest 'Arrayref input - single element' => sub {
    my ($result, $msg) = filter(
        ['single_value'],
        [],
        ['_'],  # Accept special key
    );

    ok $result, 'Validation succeeds';
    is $result->{_}, 'single_value', 'Single element stored with _ key';
};

subtest 'Arrayref input - single hashref' => sub {
    my ($result, $msg) = filter(
        [{ name => 'Grace', age => 25 }],
        ['name'],
        ['age'],
    );

    ok $result, 'Validation succeeds';
    is $result->{name}, 'Grace', 'Field from embedded hashref';
    is $result->{age}, 25, 'Field from embedded hashref';
};

subtest 'Scalar input' => sub {
    my ($result, $msg) = filter(
        'plain string',
        [],
        ['_'],
        [],  # No excluded
        1,  # Debug on
    );

    ok $result, 'Validation succeeds';
    is $result->{_}, 'plain string', 'Scalar stored with _ key';
    like $msg, qr/Plain text argument accepted with key '_'/, 'Debug warns about scalar';
};

subtest 'Debug mode - excluded fields warning' => sub {
    my ($result, $msg) = filter(
        { name => 'Henry', password => 'secret' },
        ['name'],
        ['email', 'phone'],  # Has accepted fields, so doesn't early-return
        ['password'],
        1,  # Debug on
    );

    ok $result, 'Validation succeeds';
    like $msg, qr/excluded/, 'Debug mentions excluded fields';
    like $msg, qr/'password'/, 'Debug lists excluded field';
    unlike $msg, qr/Admitted/, 'Does not say "Admitted" when there are warnings';
};

subtest 'Debug mode - unrecognized fields warning' => sub {
    my ($result, $msg) = filter(
        { name => 'Iris', extra1 => 'a', extra2 => 'b' },
        ['name'],
        ['email', 'phone'],  # Has accepted fields
        [],
        1,  # Debug on
    );

    ok $result, 'Validation succeeds';
    like $msg, qr/unrecognized/, 'Debug mentions unrecognized fields';
    unlike $msg, qr/Admitted/, 'Does not say "Admitted" when there are warnings';
};

subtest 'Debug mode - combined warnings' => sub {
    my ($result, $msg) = filter(
        { name => 'Jack', password => 'secret', spam => 'yes' },
        ['name'],
        ['email'],
        ['password'],
        1,  # Debug on
    );

    ok $result, 'Validation succeeds';
    like $msg, qr/excluded/, 'Mentions excluded fields';
    like $msg, qr/unrecognized/, 'Mentions unrecognized fields';
    unlike $msg, qr/Admitted/, 'Does not say "Admitted" when there are warnings';
};

subtest 'Empty hashref with required fields' => sub {
    my ($result, $msg) = filter(
        {},
        ['required_field'],
    );

    ok !$result, 'Validation fails';
    like $msg, qr/Unable to initialize without required arguments/, 'Error message';
};

subtest 'Return in scalar context' => sub {
    my $result = filter(
        { name => 'Kate', email => 'kate@example.com' },
        ['name', 'email'],
        [],
    );

    ok $result, 'Returns hashref in scalar context';
    is $result->{name}, 'Kate', 'Result contains data';
};

subtest 'Return in list context' => sub {
    my ($result, $msg) = filter(
        { name => 'Liam', email => 'liam@example.com' },
        ['name', 'email'],
        [],
    );

    ok $result, 'First element is hashref';
    like $msg, qr/Admitted/, 'Second element is status message';
};

subtest 'Undefined values in input' => sub {
    my ($result, $msg) = filter(
        { name => 'Mia', email => undef, phone => '555-9999' },
        ['name'],
        ['email', 'phone'],
    );

    ok $result, 'Validation succeeds';
    is $result->{name}, 'Mia', 'Defined value';
    ok !defined $result->{email}, 'Undefined value preserved';
    is $result->{phone}, '555-9999', 'Other field present';
};

subtest 'Wrong keys with correct count hits per-field existence check' => sub {
    # Two keys present, two required - but they are the wrong keys.
    # The upstream count pre-check passes; the per-field loop must catch it.
    my ($result, $msg) = filter(
        { wrong_key1 => 1, wrong_key2 => 2 },
        ['name', 'email'],
        [],
    );

    ok !$result, 'Fails when correct key count but wrong keys supplied';
    like $msg, qr/required/, 'Error message mentions required fields';
};

subtest 'Scalar input longer than 20 chars is truncated in message' => sub {
    my ($result, $msg) = filter(
        'this string is longer than twenty characters',
        [], ['_'],
    );

    ok $result, 'Long scalar input succeeds';
    like $msg, qr/\.\.\./, 'Preview truncated with ellipsis';
};

subtest 'Single-element array with long string is truncated in message' => sub {
    my ($result, $msg) = filter(
        ['this string is longer than twenty characters'],
        [], ['_'],
    );

    ok $result, 'Long single-element array input succeeds';
    like $msg, qr/\.\.\./, 'Preview truncated with ellipsis';
};

subtest 'Excluded wins over accepted in functional filter' => sub {
    # A field that appears in both the accepted and excluded lists.
    # The exclusion guard inside the accepted loop must fire.
    my ($result, $msg) = filter(
        { name => 'Nina', password => 'secret' },
        ['name'],
        ['password'],   # accepted
        ['password'],   # also excluded - excluded wins
    );

    ok $result, 'Filter succeeds';
    is $result->{name}, 'Nina', 'Required field present';
    ok !exists $result->{password}, 'Excluded wins over accepted';
};

subtest 'Returns undef in scalar context on failure' => sub {
    my $result = filter(
        { wrong => 1 },
        ['name', 'email'],
        [],
    );

    ok !defined $result, 'Returns undef (not a list) in scalar context on failure';
};

subtest 'Empty args failure returns undef in scalar context (line 863)' => sub {
    # Empty hashref: hits the `unless (keys %args)` early return in scalar context.
    my $result = filter(
        {},
        ['name'],
        [],
    );

    ok !defined $result, 'Empty input returns undef in scalar context';
};

subtest 'Per-field required check failure returns undef in scalar context (line 889)' => sub {
    # Two keys present, two required, but wrong keys: passes the count
    # pre-check at line 866, then fails the per-field loop at line 889.
    my $result = filter(
        { wrong_key1 => 1, wrong_key2 => 2 },
        ['name', 'email'],
        [],
    );

    ok !defined $result, 'Wrong keys with correct count returns undef in scalar context';
};

subtest 'Phase 4 success returns hashref in scalar context (line 953)' => sub {
    # Non-empty accepted list bypasses the fast-return at line 896,
    # so the Phase 4 return at line 953 is reached in scalar context.
    my $result = filter(
        { name => 'Oscar', email => 'oscar@example.com', phone => '555-0000' },
        ['name', 'email'],
        ['phone'],
    );

    ok $result, 'Returns hashref in scalar context via Phase 4';
    is $result->{name},  'Oscar',               'Required field present';
    is $result->{phone}, '555-0000',             'Accepted field present';
};

subtest 'Unsupported reference type yields empty args failure' => sub {
    # A ref that is neither HASH nor ARRAY (scalar ref, coderef, regex)
    # falls through all three input-parse branches, leaving %args empty,
    # which triggers the `unless (keys %args)` early return.
    for my $bad_input ( \42, sub {}, qr/foo/ ) {
        my ($result, $msg) = filter( $bad_input, ['name'], [] );
        ok !$result, ref($bad_input) . ' ref input fails';
        like $msg, qr/required/, 'Error message mentions required';
    }
};

subtest 'Fast-return with parsing message takes @all_msgs true branch (line 898)' => sub {
    # An odd-element array puts a parsing message in @messages during Phase 1.
    # With no accepted fields the fast-return fires before Phase 4, so
    # @warnings is always empty there. @all_msgs is therefore non-empty,
    # taking the true branch of `@all_msgs ? :` and returning the parsing
    # message instead of the plain "Admitted".
    my ($result, $msg) = filter(
        ['name', 'Alice', 'verbose'],   # odd: verbose => 1
        ['name'],                        # required
        [],                              # no accepted - triggers fast-return
    );

    ok $result,               'Succeeds with odd-array input and required-only filter';
    is $result->{name}, 'Alice', 'Required field present';
    like   $msg, qr/Odd number/, 'Parsing message returned via @all_msgs true branch';
    unlike $msg, qr/Admitted/,   'Plain "Admitted" not returned when messages present';
};

done_testing();
