The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Faker::Factory;

use Bubblegum::Class;
use Bubblegum::Syntax -types, -typesof, 'load', 'raise';
use Faker::Generator;

use Class::Load 'try_load_class';
use Module::Find 'findsubmod';

has locale => (
    is      => 'ro',
    isa     => typeof_str,
    default => 'en_US'
);

has namespace => (
    is      => 'ro',
    isa     => typeof_class
);

has providers => (
    is      => 'ro',
    isa     => typeof_aref,
    lazy    => 1,
    default => sub {[
        sort map load($_), findsubmod 'Faker::Provider'
    ]}
);

sub create {
    my $self      = type_obj shift;
    my $locale    = type_str (shift // $self->locale);
    my $generator = Faker::Generator->new;

    for my $provider ($self->providers->list) {
        $provider =~ s/^Faker::Provider:://;
        my $provider_class =
            $self->load_provider($provider, $locale);
        $generator->register_provider(
            $provider_class->new(generator => $generator)
        );
    }

    return $generator;
}

sub load_provider {
    my $self     = type_obj shift;
    my $provider = type_str shift;
    my $locale   = type_str shift;
    my $provider_class;

    if ($self->namespace) {
        return $provider_class
            if $provider_class = $self->load_provider_from_namespace(
                $provider, $locale
            );
    }

    return $provider_class
        if $provider_class = $self->locate_provider(
                $provider, $locale
            );

    return $provider_class
        if $provider_class = $self->locate_provider(
                $provider, 'en_US'
            );

    return $provider_class
        if $provider_class = $self->locate_provider(
                $provider
            );

    raise sprintf 'Unable to find provider "%s" with locale "%s"',
        $provider, $locale;
}

sub load_provider_from_namespace {
    my $self     = type_obj shift;
    my $provider = type_str shift;
    my $locale   = type_str shift;
    my $provider_class;

    return $provider_class
        if $provider_class = $self->locate_provider_in_namespace(
                $provider, $locale
            );

    return $provider_class
        if $provider_class = $self->locate_provider_in_namespace(
                $provider, 'en_US'
            );

    return $provider_class
        if $provider_class = $self->locate_provider_in_namespace(
                $provider
            );

    return undef;
}

sub locate_provider {
    my $self     = type_obj shift;
    my $provider = type_str shift;
    my $locale   = shift;
    my $provider_class = join '::', 'Faker::Provider',
        $locale ? ($locale, $provider) : ($provider);

    return $provider_class if try_load_class $provider_class;
    return undef;
}

sub locate_provider_in_namespace {
    my $self     = type_obj shift;
    my $provider = type_str shift;
    my $locale   = type_str shift;
    my $provider_class = join '::', $self->namespace,
        $locale ? ($locale, $provider) : ($provider);

    return $provider_class if try_load_class $provider_class;
    return undef;
}

1;