package Dist::Zilla::Plugin::DistBuild;
$Dist::Zilla::Plugin::DistBuild::VERSION = '0.003';
use Moose;
with qw/
	Dist::Zilla::Role::BuildPL
	Dist::Zilla::Role::TextTemplate
	Dist::Zilla::Role::PrereqSource
	Dist::Zilla::Role::FileGatherer
/;
use namespace::autoclean;

use experimental 'signatures', 'postderef';

use Module::Metadata;
use Moose::Util::TypeConstraints 'enum';
use MooseX::Types::Perl qw/StrictVersionStr/;
use MooseX::Types::Moose qw/Bool Str ArrayRef/;
use List::Util 1.33 qw/first any/;

sub mvp_multivalue_args { qw(header_strs footer_strs) }

sub mvp_aliases {
	+{
		header => 'header_strs',
		footer => 'footer_strs',
	}
}

around BUILDARGS => sub($orig, $class, @args) {
	my $args = $class->$orig(@args);

	my $delimiter = delete $args->{delimiter};
	if (defined $delimiter and length($delimiter)) {
		for my $arg (grep exists $args->{$_}, qw(header_strs footer_strs)) {
			s/^\Q$delimiter\E// for $args->{$arg}->@*;
		}
	}

	return $args;
};

has version_method => (
	is      => 'ro',
	isa     => enum(['installed', 'heuristic']),
	default => 'heuristic',
);

has version => (
	is      => 'ro',
	lazy    => 1,
	isa     => StrictVersionStr,
	default => sub($self) {
		if ($self->version_method eq 'installed') {
			return Module::Metadata->new_from_module('Dist::Build')->version->stringify;
		} else {
			return '0.019';
		}
	},
);

has minimum_perl => (
	is      => 'ro',
	isa     => StrictVersionStr,
	lazy    => 1,
	default => sub($self) {
		my $prereqs = $self->zilla->prereqs->cpan_meta_prereqs;
		my $reqs = $prereqs->merged_requirements([ qw/configure build test runtime/ ], ['requires']);
		return $reqs->requirements_for_module('perl') || '5.006';
	},
);

has header_strs => (
	is => 'ro', isa => ArrayRef[Str],
	traits => ['Array'],
	lazy => 1,
	default => sub { [] },
	documentation => "Additional code lines to include at the beginning of Makefile.PL",
);

has header_file => (
	is => 'ro',
	isa => Str,
	documentation => 'Additional header content to include from a file',
);

has header => (
	is            => 'ro',
	isa           => Str,
	lazy          => 1,
	builder       => '_build_header',
	documentation => "A string included at the beginning of Makefile.PL",
);

sub _build_header($self) {
	join "\n",
		$self->header_strs->@*,
		( $self->header_file
			? do {
				my $abs_file = path($self->zilla->root, $self->header_file);
				$self->log_fatal([ 'header_file %s does not exist!', $self->header_file ])
					if not $abs_file->exists;
				$abs_file->slurp_utf8
			}
			: () );
}

has footer_strs => (
	is => 'ro', isa => ArrayRef[Str],
	traits => ['Array'],
	lazy => 1,
	default => sub { [] },
	documentation => "Additional code lines to include at the end of Makefile.PL",
);

has footer_file => (
	is => 'ro',
	isa => Str,
	documentation => 'Additional footer content to include from a file',
);

has footer => (
	is            => 'ro',
	isa           => Str,
	lazy          => 1,
	builder       => '_build_footer',
	documentation => "A string included at the end of Makefile.PL",
);

sub _build_footer($self) {
	join "\n",
		$self->footer_strs->@*,
		( $self->footer_file
			? do {
				my $abs_file = path($self->zilla->root, $self->footer_file);
				$self->log_fatal([ 'footer_file %s does not exist!', $self->footer_file ])
					if not $abs_file->exists;
				$abs_file->slurp_utf8
			}
			: () );
}

has auto_configure_requires => (
	is => 'ro',
	isa => Bool,
	default => 1,
);

has auto_plugin_requires => (
	is      => 'ro',
	isa     => Bool,
	default => 0,
);

sub register_prereqs($self) {
	if ($self->auto_configure_requires) {
		$self->zilla->register_prereqs({ phase => 'configure' }, 'Dist::Build' => $self->version);
	}

	if ($self->auto_plugin_requires) {
		my @plans;
		File::Find::find(sub { push @plans, $File::Find::name if -f && /\.pl$/ }, 'planner') if -d 'planner';

		require Perl::PrereqScanner;
		require Perl::PrereqScanner::Scanner::DistBuild;
		my $scanner = Perl::PrereqScanner->new({ extra_scanners => ['DistBuild'] });
		for my $file (@plans) {
			my $prereqs = $scanner->scan_file($file);
			$prereqs->clear_requirement($_) for qw/strict warnings/;
			$self->zilla->register_prereqs({ phase => 'configure' }, %{ $prereqs->as_string_hash });
		}
	}

	return;
}

my $template = <<'BUILD_PL';
# This Build.PL for {{ $dist_name }} was generated by {{ $plugin_title }}.
use strict;
use warnings;

{{ $header }}
use {{ $minimum_perl }};
use Dist::Build{{ $version ne 0 && " $version" }};
Build_PL(\@ARGV, \%ENV);
{{ $footer }}
BUILD_PL

sub gather_files($self) {
	if (my $file = first { $_->name eq 'Build.PL' } $self->zilla->files->@*) {
		if ($file->isa('Dist::Zilla::File::OnDisk')) {
			$self->log('replacing existing Build.PL found in repository');
			$self->zilla->prune_file($file);
		}
	}

	require Dist::Zilla::File::InMemory;
	my $file = Dist::Zilla::File::InMemory->new({
		name => 'Build.PL',
		content => $template,    # template evaluated later
	});

	$self->add_file($file);
	return;
}

sub setup_installer($self) {
	my @sharedir;

	for my $map (map { $_->share_dir_map } $self->zilla->plugins_with(-ShareDir)->@*) {
		push @sharedir, sprintf "dist_sharedir('%s');", quotemeta $map->{dist} if defined $map->{dist};
		for my $module (keys %{ $map->{module} }) {
			push @sharedir, sprintf "module_sharedir('%s', '%s');", $map->{module}{$module} =~ s{[\\']}{\\$1}gr, $module;
		}
	}

	if (@sharedir) {
		unshift @sharedir, "load_extension('Dist::Build::ShareDir');";
		my $content = join '', map "$_\n", @sharedir;
		my $file = Dist::Zilla::File::InMemory->new({
			name    => 'planner/sharedir.pl',
			content => $content,
		});
		$self->add_file($file);
	}

	my @exedir = map { sprintf "script_dir('%s');", $_->dir } $self->zilla->plugins_with(-ExecFiles)->@*;
	if (@exedir) {
		unshift @exedir, "load_extension('Dist::Build::Core');";
		my $content = join '', map "$_\n", @exedir;
		my $file = Dist::Zilla::File::InMemory->new({
			name    => 'planner/scriptdir.pl',
			content => $content,
		});
		$self->add_file($file);
	}

	my $file = first { $_->name eq 'Build.PL' } $self->zilla->files->@*;
	my $content = $file->content;

	$content = $self->fill_in_string($content, {
			version      => $self->version,
			minimum_perl => $self->minimum_perl,
			dist_name    => $self->zilla->name,
			plugin_title => ref($self) . ' ' . ($self->VERSION || '<self>'),
			header       => $self->header,
			footer       => $self->footer,
		});

	$self->log_debug([ 'updating contents of Build.PL in memory' ]);
	$file->content($content);

	return;
}

__PACKAGE__->meta->make_immutable;

1;

# ABSTRACT: Build a Build.PL that uses Dist::Build


# vim: set ts=4 sw=4 noet nolist :

__END__

=pod

=encoding UTF-8

=head1 NAME

Dist::Zilla::Plugin::DistBuild - Build a Build.PL that uses Dist::Build

=head1 VERSION

version 0.003

=head1 DESCRIPTION

This plugin will create a F<Build.PL> for installing the dist using L<Dist::Build|Dist::Build>.

If needed it will also create a F<planner/sharedir.pl> file to integrate all sharedirs known to C<Dist::Zilla>.

=head1 ATTRIBUTES

=head2 version

B<Optional:> Specify the minimum version of L<Dist::Build|Dist::Build> to depend on.

Defaults to the version determined by C<version_method>.

=head2 version_method

This attribute determines how the default minimum perl is detected. It has two possible values:

=over 4

=item * installed

This will give the version installed on the author's perl installation.

=item * heuristic

This will return a heuristically determined minimum version of C<Dist::Build>.

=back

=head2 auto_plugin_requires

If true, this plugin will try to automatically scan for the dependencies of your F<planner/> scripts. Defaults to false.

=head2 minimum_perl

B<Optional:> Specify the minimum version of perl to require in the F<Build.PL>.

This is normally taken from dzil's prereq metadata.

=head2 header

A line of code which is included near the top of F<Build.PL>.  Can be used more than once.

=head2 footer

A line of code which is included at the bottom of F<Build.PL>.  Can be used more than once.

=head2 delimiter

A string, usually a single character, which is stripped from the beginning of
all C<header>, and C<footer> lines. This is because the
INI file format strips all leading whitespace from option values, so including
this character at the front allows you to use leading whitespace in an option
string.  This is helpful for the formatting of F<Build.PL>s, but a nice thing
to have when inserting any block of code.

=head1 AUTHOR

Leon Timmermans <fawaka@gmail.com>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2024 by Leon Timmermans.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
