#!/usr/bin/env perl

use strict;
use warnings;

use Data::Dumper;
use Getopt::Long;
use File::Path qw(make_path);
use Pod::Usage;
use FindBin;
use lib "$FindBin::Bin/../lib";

use App::Test::Generator::SchemaExtractor;
use App::Test::Generator::Planner;
use App::Test::Generator::Emitter::Perl;

=head1 NAME

extract-schemas2 - Extract schemas and optionally emit Perl tests

=head1 SYNOPSIS

    extract-schemas2 [options] <module.pm>

    Options:
      --output-dir DIR       Directory for schema files (default: schemas/)
      --emit-tests           Generate Perl tests from schemas
      --test-dir DIR         Output directory for generated tests (default: t/generated/)
      --strict-pod=off|warn|fatal
      --verbose
      --help
      --man

=head1 DESCRIPTION

This tool extracts method schemas from a Perl module and can optionally
generate Perl test files using App::Test::Generator::Emitter::Perl.

=cut

# ----------------------------------------------------------------------
# CLI Options
# ----------------------------------------------------------------------

my %cli_opts = (
    help => 0,
    man  => 0,
);

my %opts = (
    output_dir => 'schemas',
    test_dir   => 't/generated',
    strict_pod => 'warn',
    verbose    => 0,
    emit_tests => 0,
);

GetOptions(
    'output-dir|o=s' => \$opts{output_dir},
    'emit-tests|e'   => \$opts{emit_tests},
    'test-dir|t=s'   => \$opts{test_dir},
    'strict-pod|s=s' => \$opts{strict_pod},
    'verbose|v'      => \$opts{verbose},
    'help|h'         => \$cli_opts{help},
    'man|m'          => \$cli_opts{man},
) or pod2usage(2);

pod2usage(-exitval => 0, -verbose => 1) if $cli_opts{help};
pod2usage(-exitval => 0, -verbose => 2) if $cli_opts{man};

if ($opts{strict_pod} !~ /^(off|warn|fatal)$/) {
    die "Invalid --strict-pod value '$opts{strict_pod}'. Expected off, warn, or fatal\n";
}

my $input_file = shift @ARGV
    or pod2usage('Error: No input file specified');

die "Error: File not found: $input_file\n" unless -f $input_file;

# ----------------------------------------------------------------------
# Extraction Phase
# ----------------------------------------------------------------------

print "Extracting schemas from: $input_file\n";
print "Schema output directory: $opts{output_dir}\n";

make_path($opts{output_dir}) unless -d $opts{output_dir};

my $extractor = App::Test::Generator::SchemaExtractor->new(
    input_file => $input_file,
    %opts,
);

my $schemas = $extractor->extract_all();

# ----------------------------------------------------------------------
# Summary
# ----------------------------------------------------------------------

print "\n", '=' x 70, "\n";
print "EXTRACTION SUMMARY\n";
print '=' x 70, "\n\n";

my %confidence = (
    input  => { high => 0, medium => 0, low => 0 },
    output => { high => 0, medium => 0, low => 0 },
);

foreach my $method (sort keys %$schemas) {
    my $schema = $schemas->{$method};

    my $iconf = $schema->{_confidence}{input}{level}  // 'low';
    my $oconf = $schema->{_confidence}{output}{level} // 'low';

    $confidence{input}{$iconf}++;
    $confidence{output}{$oconf}++;

    my $param_count =
        scalar grep { $_ !~ /^_/ } keys %{ $schema->{input} };

    printf "%-30s %2d params  [%s input] [%s output]\n",
        $method,
        $param_count,
        uc($iconf),
        uc($oconf);
}

print "\nTotal methods: ", scalar(keys %$schemas), "\n";

foreach my $phase (qw(input output)) {
    print ucfirst($phase), " confidence:\n";
    foreach my $level (qw(high medium low)) {
        printf "  %-6s %d\n", ucfirst($level), $confidence{$phase}{$level};
    }
}

# ----------------------------------------------------------------------
# Test Emission Phase
# ----------------------------------------------------------------------

if ($opts{emit_tests}) {

    print "\nGenerating Perl tests...\n";
    print "Test output directory: $opts{test_dir}\n";

    make_path($opts{test_dir}) unless -d $opts{test_dir};

    # ------------------------------------------------------------
    # Derive package name from input file
    # ------------------------------------------------------------

    open my $fh, '<', $input_file
        or die "Cannot open $input_file: $!";

    my $package_name;
    while (<$fh>) {
        if (/^\s*package\s+([\w:]+)\s*;/) {
            $package_name = $1;
            last;
        }
    }
    close $fh;

    die "Could not determine package name from $input_file\n"
        unless $package_name;

    # ------------------------------------------------------------
    # Generate very basic plans from schema
    # ------------------------------------------------------------

    my %plans;

    foreach my $method (keys %$schemas) {
        my $schema = $schemas->{$method};

        my $accessor = $schema->{accessor}{type} // '';

        $plans{$method} = {
            basic_test       => 1,
            getter_test      => $accessor eq 'getter'  ? 1 : 0,
            setter_test      => $accessor eq 'setter'  ? 1 : 0,
            getset_test      => $accessor eq 'getset'  ? 1 : 0,
            chaining_test    => $accessor eq 'setter'  ? 1 : 0,
            error_handling_test => 0,
            context_tests    => 0,
        };
    }

# ----------------------------------------------------------
# Planning Phase
# ----------------------------------------------------------

print "Planning tests...\n" if $opts{verbose};

my $planner = App::Test::Generator::Planner->new(schemas => $schemas);

my $plans = $planner->plan_all();

# ----------------------------------------------------------
# Emission Phase
# ----------------------------------------------------------

	my $emitter = App::Test::Generator::Emitter::Perl->new(
		package => $package_name,
		schema  => $schemas,
		plans   => $plans,
	);

	my $code = $emitter->emit();

    # ------------------------------------------------------------
    # Write output file
    # ------------------------------------------------------------

    my $test_file = "$opts{test_dir}/01-basic.t";

    open my $out, '>', $test_file
        or die "Cannot write $test_file: $!";

    print $out $code;
    close $out;

    print "Wrote test file: $test_file\n";
}


if ($opts{verbose}) {
    print "\nSchemas structure:\n";
    print Dumper($schemas);
}

print "\nDone.\n";

__END__

=head1 EXAMPLES

=head2 Extract Only

    extract-schemas2 lib/MyModule.pm

=head2 Extract and Generate Tests

    extract-schemas2 --emit-tests lib/MyModule.pm

=head2 Custom Test Directory

    extract-schemas2 --emit-tests --test-dir t/auto lib/MyModule.pm

=head1 SEE ALSO

L<App::Test::Generator>,
L<App::Test::Generator::SchemaExtractor>,
L<App::Test::Generator::Emitter::Perl>

=head1 AUTHOR

Nigel Horne

=cut
