## no critic (ControlStructures::ProhibitPostfixControls)
package Dist::Zilla::Plugin::Test::WeaveFile;

use strict;
use warnings;
use 5.010;

# ABSTRACT: Author tests to verify woven project files are up to date

our $VERSION = '0.001';

use Moose;
use Dist::Zilla::File::InMemory;
use Sub::Exporter::ForMethods 'method_installer';
use Data::Section 0.004 { installer => method_installer }, '-setup';
use namespace::autoclean;
use Path::Tiny qw( path );

use Dist::Zilla::Plugin::WeaveFile::Engine;

with( 'Dist::Zilla::Role::FileGatherer', 'Dist::Zilla::Role::TextTemplate', 'Dist::Zilla::Role::PrereqSource', );

has filepath_template => (
    is  => 'ro',
    isa => 'Str',
    ## no critic (ValuesAndExpressions::RequireInterpolationOfMetachars)
    default => 'xt/author/weave_{{ $file }}.t',
);

has _test_files => (
    is      => 'ro',
    lazy    => 1,
    default => sub {
        my $self  = shift;
        my $zilla = $self->zilla;

        my @plugins = grep { $_->isa('Dist::Zilla::Plugin::WeaveFile') && $_->plugin_name ne 'WeaveFile' } @{ $zilla->plugins };

        my $dist_meta = {
            name     => $zilla->name,
            abstract => $zilla->abstract,
            author   => $zilla->authors->[0],
            authors  => $zilla->authors,
            version  => $zilla->version,
        };

        my @files;
        for my $plugin (@plugins) {
            my $engine = Dist::Zilla::Plugin::WeaveFile::Engine->new(
                config_path => $plugin->config,
                root_dir    => "${\$zilla->root}",
                dist        => $dist_meta,
            );

            my $expected  = $engine->render_file( $plugin->file );
            my $safe_name = $plugin->file;
            $safe_name =~ s/[^[:upper:][:lower:][:digit:]_]/_/gmsx;

            my $test_path = $self->fill_in_string( $self->filepath_template, { file => $safe_name }, );

            my $content =
              $self->fill_in_string( ${ $self->section_data('test-weave') }, { filepath => $plugin->file }, ) . $expected;

            push @files,
              Dist::Zilla::File::InMemory->new(
                name    => $test_path,
                content => $content,
              );
        }

        return \@files;
    },
);

sub mvp_multivalue_args { return () }

sub gather_files {
    my $self = shift;
    $self->add_file($_) for @{ $self->_test_files };
    return;
}

sub register_prereqs {
    my $self = shift;
    $self->zilla->register_prereqs(
        { type => 'requires', phase => 'develop' },
        'Test2::V1'  => 0,
        'Path::Tiny' => 0,
    );
    return;
}

around BUILDARGS => sub {
    my ( $orig, $class, @arg ) = @_;
    my $args  = $class->$orig(@arg);
    my %copy  = %{$args};
    my $zilla = delete $copy{zilla};
    my $name  = delete $copy{plugin_name};
    my %known;
    for my $key (qw( filepath_template )) {
        $known{$key} = delete $copy{$key} if exists $copy{$key};
    }
    if (%copy) {
        $zilla->log_fatal(
            [ 'Unknown configuration option(s) in %s: %s', __PACKAGE__ . ' / ' . $name, join( q{,}, keys %copy ), ] );
    }
    return {
        zilla       => $zilla,
        plugin_name => $name,
        %known,
    };
};

__PACKAGE__->meta->make_immutable;
1;

=pod

=encoding UTF-8

=head1 NAME

Dist::Zilla::Plugin::Test::WeaveFile - Author tests to verify woven project files are up to date

=head1 VERSION

version 0.001

=head1 SYNOPSIS

In your F<dist.ini>:

=head1 DESCRIPTION

This L<Dist::Zilla> plugin creates author test files that verify project
files generated by C<[WeaveFile / ...]> plugins are present and match
their definitions.

For each C<[WeaveFile / ...]> plugin found in F<dist.ini>, a test file is
generated in F<xt/author/> that compares the actual file content against
the expected content rendered from the weave config.

=for Pod::Coverage gather_files register_prereqs mvp_multivalue_args

=for test_synopsis BEGIN { die "SKIP: skip this pod!\n"; }

    [WeaveFile / README.md]
    config = .weavefilerc

    [Test::WeaveFile]

=head1 ATTRIBUTES

=head2 filepath_template

Template for test file paths. Defaults to
C<xt/author/weave_{{ $file }}.t>.

=head1 AUTHOR

Mikko Koivunalho <mikkoi@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2025 by Mikko Koivunalho.

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

__DATA__
__[ test-weave ]__
#!perl

use strict;
use warnings;

use Test2::V1 qw( -utf8 -x );
use Test2::Plugin::BailOnFail;
use English qw( -no_match_vars );
use Path::Tiny qw( path );

T2->ok(path('{{ $filepath }}')->is_file(), 'File {{ $filepath }} exists');

my $got = path('{{ $filepath }}')->slurp_utf8 =~ s/[[:space:]]+$//rmsx;

my (@got_lines, @expected_lines);
foreach (split qr{\R}msx, $got) { push @got_lines, $_; }
do {
    local $INPUT_RECORD_SEPARATOR = undef;
    my $expected = <DATA>;
    $expected =~ s/[[:space:]]+$//msx;
    foreach (split qr{\R}msx, $expected) { push @expected_lines, $_; }
};
T2->is(\@got_lines, \@expected_lines, 'File {{ $filepath }} matches expected content');

T2->done_testing;

__DATA__
