#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
use HTTP::Tiny;
use JSON::PP;
use Getopt::Long;
use Module::CoreList;
use Pod::Usage;

# Get options
my $verbose = 0;
my $native_class_only = 0;
my $help = 0;
GetOptions(
  "v|verbose"         => \$verbose,
  "native-class-only" => \$native_class_only,
  "h|help"            => \$help,
);

if ($help) {
  Pod::Usage::pod2usage(0);
}

my %seen;    # To avoid infinite recursion
my %results; # $results{$module_name} = 1
my $json = JSON::PP->new->utf8;
my $http = HTTP::Tiny->new(timeout => 10);

# Main process
my $target = shift or die "Usage: $0 [OPTIONS] Module::Name\n";

print STDERR "--- Analyzing dependencies for $target ---\n" if $verbose;
# Pass all context as arguments to avoid closures
get_dependencies_recursive($target, 0, $native_class_only, $target, \%results, \%seen, $http, $json, $verbose);
print STDERR "--- Done ---\n" if $verbose;

# Output verified unique module names
for my $module_name (sort keys %results) {
  print "$module_name\n";
}

sub get_dependencies_recursive {
  my ($name, $level, $native_class_only, $target, $results_ref, $seen_ref, $http, $json, $verbose) = @_;
  $level //= 0;

  # Avoid redundant network access
  return if $seen_ref->{$name}++;

  # Skip core modules
  return if Module::CoreList::is_core($name, undef, $]);

  # Get module info from MetaCPAN
  my $url = "http://fastapi.metacpan.org/v1/module/$name";
  my $res = $http->get($url);

  unless ($res->{success}) {
    # If module endpoint fails, try it as a distribution name
    fetch_from_release($name, $name, $level, $native_class_only, $target, $results_ref, $seen_ref, $http, $json, $verbose);
    return;
  }

  my $module_data = eval { $json->decode($res->{content}) };
  return if $@ || !$module_data;

  my $dist = $module_data->{distribution};
  fetch_from_release($name, $dist, $level, $native_class_only, $target, $results_ref, $seen_ref, $http, $json, $verbose);
}

sub fetch_from_release {
  my ($module_name, $dist, $level, $native_class_only, $target, $results_ref, $seen_ref, $http, $json, $verbose) = @_;
  my $indent = "  " x $level;

  # Get release info
  my $url = "http://fastapi.metacpan.org/v1/release/$dist";
  my $res = $http->get($url);
  return unless $res->{success};

  my $rel_data = eval { $json->decode($res->{content}) };
  return if $@ || !$rel_data;

  my $version = $rel_data->{version};
  
  # Scan Makefile.PL/Build.PL if requested
  my $is_native = 0;
  if ($native_class_only) {
    # Get the author ID from release data
    my $author = $rel_data->{author}; 
    
    for my $file ('Makefile.PL', 'Build.PL') {
      # Use the correct path: /source/AUTHOR/DIST-VERSION/FILE
      my $src_url = "http://fastapi.metacpan.org/v1/source/$author/$dist-$version/$file";
      my $src_res = $http->get($src_url);
      
      # Check if SPVM::Builder is used for native build rules
      if ($src_res->{success} && $src_res->{content} =~ /SPVM::Builder::Util::API::create_make_rule/) {
        $is_native = 1;
        last;
      }
    }
  }
  
  # Store the confirmed module in results (filter here instead of output loop)
  if ($module_name ne $target) {
    if ($native_class_only) {
      $results_ref->{$module_name} = 1 if $is_native;
    }
    else {
      $results_ref->{$module_name} = 1;
    }
    
    if ($verbose) {
      print STDERR "${indent}- $module_name";
      print STDERR " (Dist: $dist)" if ($dist =~ s/-/::/gr) ne $module_name;
      unless ($results_ref->{$module_name}) {
        print STDERR " -- SKIP";
        if ($native_class_only) {
          print STDERR ": does not contain native or precompile classes";
        }
      }
      print STDERR "\n";
    }
    
  }

  # Traverse dependencies
  my $deps = $rel_data->{dependency} || [];
  for my $dep (@$deps) {
    if ($dep->{phase} eq 'runtime' && $dep->{relationship} eq 'requires') {
      # Use clearer name: module_name from MetaCPAN API remains 'module'
      my $next_module_name = $dep->{module};
      next if !$next_module_name || $next_module_name eq 'perl';
      # Recursively pass all arguments
      get_dependencies_recursive($next_module_name, $level + 1, $native_class_only, $target, $results_ref, $seen_ref, $http, $json, $verbose);
    }
  }
}

__END__

=head1 NAME

cpandeps - A tool to list non-core CPAN dependencies recursively.

=head1 Usage

  Usage: cpandeps [OPTIONS] Module::Name

    cpandeps SPVM::JSON

    # Only show modules with native or precompile classes
    cpandeps --native-class-only SPVM::JSON

    # Use with xargs for reinstallation
    cpandeps SPVM::JSON | xargs cpanm --reinstall

  Options:
    -h, --help                Shows this message
    -v, --verbose             Shows recursive dependency tree to STDERR
    --native-class-only       Outputs only modules containing native/precompile rules

=cut
