package System::Introspector::Report::Source;
use Moo;
use JSON::PP;
use IO::All;
use Module::Runtime qw( use_module );
use Log::Contextual::WarnLogger;
use Log::Contextual qw( :log ),
  -default_logger => Log::Contextual::WarnLogger->new({
    env_prefix => 'SI_REPORT_SOURCE',
    levels      => [qw( error fatal warn )],
  });

my $_json = JSON::PP->new->utf8->relaxed->allow_nonref;

has root => (is => 'ro', required => 1);

sub new_from_root {
  my ($class, $root, @args) = @_;
  if ($root =~ m{^(.+):(.+)$}) {
    my ($remote, $remote_root) = ($1, $2);
    require Object::Remote;
    return $class->new::on($remote, @args, root => $remote_root);
  }
  else {
    return $class->new(@args, root => $root);
  }
}

sub generate {
  my ($self, @types) = @_;
  my @generators = map { $self->_make_generator($_) } @types;
  my $required = +{ map { ($_ => 1) } map $_->required_data, @generators };
  my @dirs = $self->_find_source_dirs;
  for my $dir_spec (@dirs) {
    my ($id, $path) = @$dir_spec;
    my $data = $self->_load_dataset($path, $required)
      or next;
    log_trace { "loading node '$id' from '$path'" };
    $_->collect_from($id, $data)
      for @generators;
  }
  return map { ($_->render_reports) } @generators;
}

sub _load_dataset {
  my ($self, $path, $required) = @_;
  my $root = $self->root;
  return +{
    map {
      ($_->[0], $_json->decode(scalar $_->[1]->slurp) || {});
    } grep {
      $required->{$_->[0]};
    } map {
      my $key = $_;
      $key =~ s!^\Q$root\E/*(?:[^/]+/+){2}!!;
      $key =~ s{\.json$}{};
      [$key, $_];
    } grep {
      not(m{^\.}) and m{\.json$};
    } $path->deep->all_files
  };
}

sub _find_source_dirs {
  my ($self) = @_;
  my $root = $self->root;
  return map {
    (my $id = $_) =~ s{^\Q$root\E/*}{};
    [$id, $_];
  } io($root)->all_dirs;
}

sub _make_generator {
  my ($self, $spec) = @_;
  my ($type, $args) = @$spec;
  return use_module("System::Introspector::Report::Builder::$type")
    ->new($args || {});
}

1;
