The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Dist::Maker::Config;
use Mouse;
use MouseX::StrictConstructor;

use Dist::Maker::Util qw(save);

# data = default config + user's config (from file) + application data

use File::Basename ();
use File::Spec;

with 'Dist::Maker::Logger';

has data => (
    is      => 'ro',
    isa     => 'HashRef',
    lazy    => 1,
    default => sub {
        my($config) = @_;
        my $data = {
            software => File::Basename::basename($0),
            core => {
                verbose  => 0,
            },
            module => {
                initial_version => '0.01',

            },
            template => {
                default   => 'Default',
                module    => [qw(Time::Piece)],
                dist_init => 1,
            },
            user     => {
                name  => '<<YOUR NAME HERE>>',
                email => '<<YOUR EMAIL ADDRESS HERE>>',
            },
            namespace => [],
        };
        $config->merge_data($data, $config->user_data);
        return $data;
    },
);

has user_data => (
    is      => 'ro',
    isa     => 'HashRef',
    lazy    => 1,
    default => sub {
        my($config) = @_;

        my $file = $config->config_file;
        my $new  = do $file || {};
        return $new;
    },
);

has home_dir => (
    is      => 'ro',
    isa     => 'Str',
    default => sub { "$ENV{HOME}/.dim" },
);

has verbose => (
    is      => 'rw',
    isa     => 'Int',
    lazy    => 1,
    default => sub {
        my($self) = @_;
        return $self->data->{core}{verbose};
    },
);

has config_file => (
    is       => 'rw',
    isa      => 'Str',
    lazy     => 1,
    default  => sub {
        my($config) = @_;
        return $config->path('config.pl');
    },
);

has distconfig_file => (
    is       => 'rw',
    isa      => 'Str',
    lazy     => 1,
    default  => sub {
        my($config) = @_;
        return '.dimconfig';
    },
);

sub path {
    my($config, $basename) = @_;
    return File::Spec->catfile(
        $config->home_dir,
        $basename,
    );
}

sub load_class {
    my($config, $parts) = @_;
    my @namespaces;
    if(my $namespace = $config->data->{namespace}) {
        unshift @namespaces, ref($namespace) eq 'ARRAY'
            ? @{$namespace}
            :   $namespace;
    }
    return Mouse::Util::load_first_existing_class(
        map { join '::', $_, $parts }
            @namespaces, 'Dist::Maker');
}

sub read_config_from_file {
    my($config, $file) = @_;
    my $new = do $file || {};
    $config->add_data($new);
    return;
}

sub _merge_array {
    my($base, $new) = @_;
    push @{$base}, ref($new) eq 'ARRAY' ? @{$new} : $new;
    return;
}

sub _merge_hash {
    my($base, $new) = @_;
    while(my($k, $v) = each %{$new}) {
        my $b = $base->{$k};
        if(ref $b eq 'HASH') {
            _merge_hash($b, $v);
        }
        elsif(ref $b eq 'ARRAY') {
            _merge_array($b, $v);
        }
        else {
            $base->{$k} = $v;
        }
    }
    return;
}

sub add_user_data {
    my($config, $data) = @_;
    _merge_hash($config->user_data, $data);
    return;
}

sub add_data {
    my($config, $data) = @_;
    _merge_hash($config->data, $data);
    return;
}

sub merge_data {
    my($self, @data) = @_;
    my %vars;
    foreach my $d(@data) {
        _merge_hash(\%vars, $d);
    }
    return \%vars;
}

sub dump_data {
    my($self, $data) = @_;
    require Data::Dumper;
    my $dd = Data::Dumper->new([$data]);
    $dd->Quotekeys(0);
    $dd->Sortkeys(1);
    $dd->Terse(1);
    $dd->Indent(1);
    return $dd->Dump();
}


sub save_data {
    my($config, $file, $data) = @_;
    $data //= $config->user_data;
    $file //= $config->config_file;

    my $home = $config->home_dir();
    if(not -e $home) {
        mkdir $home       or warn "Cannot mkdir $home: $!";
        chmod 0700, $home or warn "Cannot chmod $home: $!";
    }

    my $header = "#!perl\n" . "# This file is managed by $0.\n";
    $config->save( $file => $header . $config->dump_data($data) )
        or die "Cannot save config file";
    chmod 0600, $file;
}

sub save_data_to_distconfig {
    my($config, $distdir, $data) = @_;

    $config->save_data( "$distdir/" . $config->distconfig_file, $data);
}


no Mouse;
__PACKAGE__->meta->make_immutable();

__END__

=head1 NAME

Dist::Maker::Config - Configuration variables

=head1 VARIABLES

=head2 core.verbose

Logging level.
Default to C<3>.

=head2 user.name

Your name used as the author name.
Available as C<< <: $user.name :> >> in templates.

=head2 user.email

Your email address used as the author's email address.
Available as C<< <: $user.email :> >> in templates.

=head2 template.default

A template name used template by default.
Default to C<Default>.

=head2 template.module

Modules used in templates, i.e. C<< Text::Xslate->new( module => $module ) >>.
Default to C<< ['Time::Piece'] >>.

=head2 template.dist_init

If true (default), C<< dim init >> will does extra things, e.g.
C<< perl Makefile.PL && make manifest >> by the Default template.

=head2 pause.user

PAUSE ID used in C<ship> subcommand (NOT YET IMPLEMENTED).

=head2 pause.password

PAUSE password used in C<ship> subcommand (NOT YET IMPLEMENTED).

=head2 module.initial_version

The initial version of modules.
Default to C<0.01>.

=cut