package Dist::Zilla::Plugin::MAXMIND::TidyAll;

use v5.10;

use strict;
use warnings;
use autodie;
use namespace::autoclean;

our $VERSION = '0.83';

use Code::TidyAll::Config::INI::Reader 0.44;
use List::Util 1.45 qw( uniqstr );
use Path::Iterator::Rule;
use Path::Tiny qw( path );
use Perl::Critic::Freenode 0.021;
use Perl::Critic::Moose 1.05;
use Sort::ByExample qw( sbe );

use Moose;

with qw(
    Dist::Zilla::Role::BeforeBuild
    Dist::Zilla::Role::TextTemplate
);

my $perltidyrc = <<'EOF';
--blank-lines-before-packages=0
--iterations=2
--no-outdent-long-comments
-bar
-boc
-ci=4
-i=4
-l=78
-nolq
-se
-wbb="% + - * / x != == >= <= =~ !~ < > | & >= < = **= += *= &= <<= &&= -= /= |= >>= ||= .= %= ^= x="
EOF

my $perlcriticrc = <<'EOF';
severity = 3
verbose = 11

theme = core + pbp + bugs + maintenance + cosmetic + complexity + security + tests + moose

program-extensions = pl psgi t

exclude = Subroutines::ProhibitCallsToUndeclaredSubs

[BuiltinFunctions::ProhibitStringySplit]
severity = 3

[CodeLayout::RequireTrailingCommas]
severity = 3

[ControlStructures::ProhibitCStyleForLoops]
severity = 3

[Documentation::RequirePackageMatchesPodName]
severity = 3

[Freenode::WhileDiamondDefaultAssignment]
set_themes = core

[InputOutput::RequireCheckedSyscalls]
functions = :builtins
exclude_functions = sleep
severity = 3

[Moose::RequireCleanNamespace]
modules = Moose Moose::Role MooseX::Role::Parameterized Moose::Util::TypeConstraints
cleaners = namespace::autoclean

[NamingConventions::Capitalization]
package_exemptions = [A-Z]\w+|main
file_lexical_variables = [A-Z]\w+|[^A-Z]+
global_variables = :starts_with_upper
scoped_lexical_variables = [A-Z]\w+|[^A-Z]+
severity = 3

# Given our code base, leaving this at 5 would be a huge pain
[Subroutines::ProhibitManyArgs]
max_arguments = 10

[RegularExpressions::ProhibitComplexRegexes]
max_characters = 200

[RegularExpressions::ProhibitUnusualDelimiters]
severity = 3

[Subroutines::ProhibitUnusedPrivateSubroutines]
private_name_regex = _(?!build)\w+

[TestingAndDebugging::ProhibitNoWarnings]
allow = redefine

[ValuesAndExpressions::ProhibitEmptyQuotes]
severity = 3

[ValuesAndExpressions::ProhibitInterpolationOfLiterals]
severity = 3

[ValuesAndExpressions::RequireUpperCaseHeredocTerminator]
severity = 3

[Variables::ProhibitPackageVars]
add_packages = Test::Builder

[-ControlStructures::ProhibitCascadingIfElse]

[-ErrorHandling::RequireCarping]
[-InputOutput::RequireBriefOpen]

[-ValuesAndExpressions::ProhibitConstantPragma]

# No need for /xsm everywhere
[-RegularExpressions::RequireDotMatchAnything]
[-RegularExpressions::RequireExtendedFormatting]
[-RegularExpressions::RequireLineBoundaryMatching]

# by concensus in standup 2015-05-12 we decided to allow return undef
# this is mainly so bar can be written to return undef so that
# foo( bar => bar(), bazz => baz() ) won't cause problems
[-Subroutines::ProhibitExplicitReturnUndef]

# This incorrectly thinks signatures are prototypes.
[-Subroutines::ProhibitSubroutinePrototypes]

# http://stackoverflow.com/questions/2275317/why-does-perlcritic-dislike-using-shift-to-populate-subroutine-variables
[-Subroutines::RequireArgUnpacking]

[-Subroutines::RequireFinalReturn]

# "use v5.14" is more readable than "use 5.014"
[-ValuesAndExpressions::ProhibitVersionStrings]
EOF

sub before_build {
    my $self = shift;

    path('tidyall.ini')->spew( $self->_tidyall_ini_content );

    $self->_maybe_write_file( 'perlcriticrc', $perlcriticrc );
    $self->_maybe_write_file( 'perltidyrc',   $perltidyrc );

    return;
}

sub _tidyall_ini_content {
    my $self = shift;

    return $self->_new_tidyall_ini
        unless -e 'tidyall.ini';

    return $self->_munged_tidyall_ini;
}

sub _new_tidyall_ini {
    my $self = shift;

    my $perl_select = '**/*.{pl,pm,t,psgi}';
    my %tidyall     = (
        'PerlTidy' => {
            select => [$perl_select],
            ignore => [ $self->_default_perl_ignore ],
        },
        'PerlCritic' => {
            select => [$perl_select],
            ignore => [ $self->_default_perl_ignore ],
        },
        'Test::Vars' => {
            select => ['**/*.pm'],
            ignore => [ $self->_default_perl_ignore ],
        },
    );

    return $self->_config_to_ini( \%tidyall );
}

sub _munged_tidyall_ini {
    my $self = shift;

    my $tidyall
        = Code::TidyAll::Config::INI::Reader->read_file('tidyall.ini');

    my %has_default_ignore
        = map { $_ => 1 } qw( PerlTidy PerlCritic Test::Vars );
    for my $section ( grep { $has_default_ignore{$_} } sort keys %{$tidyall} )
    {
        $tidyall->{$section}{ignore} = [
            uniqstr(
                @{ $tidyall->{$section}{ignore} },
                $self->_default_perl_ignore,
            )
        ];
    }

    return $self->_config_to_ini($tidyall);
}

sub _default_perl_ignore {
    my $self = shift;

    my @ignore = qw(
        .build/**/*
        blib/**/*
        t/00-*
        t/author-*
        t/release-*
        t/zzz-*
        xt/**/*
    );

    my $dist = $self->zilla->name;
    push @ignore, "$dist-*/**/*";

    return @ignore;
}

sub _config_to_ini {
    my $self    = shift;
    my $tidyall = shift;

    my @xt_files = Path::Iterator::Rule->new->file->name(qr/\.t$/)->all('xt');

    if (@xt_files) {
        my $suffix = 'non-auto-generated xt';
        for my $plugin (qw( PerlCritic PerlTidy )) {
            $tidyall->{ $plugin . q{ } . $suffix }{select} = \@xt_files;
        }
    }

    for my $section ( keys %{$tidyall} ) {
        if ( $section =~ /PerlTidy/ ) {
            $tidyall->{$section}{argv} = '--profile=$ROOT/perltidyrc';
        }
        elsif ( $section =~ /PerlCritic/ ) {
            $tidyall->{$section}{argv} = '--profile=$ROOT/perlcriticrc';
        }
    }

    my $sorter = sbe(
        [ 'select', 'ignore' ],
        {
            fallback => sub { $_[0] cmp $_[1] },
        },
    );

    my $ini = q{};
    for my $section ( sort keys %{$tidyall} ) {
        $ini .= "[$section]\n";

        for my $key ( $sorter->( keys %{ $tidyall->{$section} } ) ) {
            for my $val (
                sort ref $tidyall->{$section}{$key}
                ? @{ $tidyall->{$section}{$key} }
                : $tidyall->{$section}{$key}
                ) {

                $ini .= "$key = $val\n";
            }
        }

        $ini .= "\n";
    }

    chomp $ini;

    return $ini;
}

sub _maybe_write_file {
    my $self    = shift;
    my $file    = shift;
    my $content = shift;

    return if -e $file;

    path($file)->spew($content);

    return;
}

__PACKAGE__->meta->make_immutable;

1;

# ABSTRACT: Creates default tidyall.ini, perltidyrc, and perlcriticrc files if they don't yet exist

__END__

=pod

=encoding UTF-8

=head1 NAME

Dist::Zilla::Plugin::MAXMIND::TidyAll - Creates default tidyall.ini, perltidyrc, and perlcriticrc files if they don't yet exist

=head1 VERSION

version 0.83

=for Pod::Coverage .*

=head1 SUPPORT

Bugs may be submitted through L<https://github.com/maxmind/Dist-Zilla-PluginBundle-MAXMIND/issues>.

=head1 AUTHOR

Dave Rolsky <autarch@urth.org>

=head1 COPYRIGHT AND LICENSE

This software is Copyright (c) 2017 by Dave Rolsky and MaxMind, Inc.

This is free software, licensed under:

  The Artistic License 2.0 (GPL Compatible)

=cut