#!/usr/bin/perl

use strict;
use Dpkg::IPC;
use Debian::PkgJs::Banned;
use Debian::PkgJs::Cache;
use Debian::PkgJs::Dependencies;
use Debian::PkgJs::Utils;
use Debian::PkgJs::Version;
use Getopt::Long;
use JSON;
use Progress::Any '$progress';
use Progress::Any::Output;

Progress::Any::Output->set( 'TermProgressBarColor',
    template =>
'<color ffff00>%p%</color> <color 808000>[</color>%B<color 808000>]</color>'
);

my %opt;

GetOptions(
    \%opt, qw(
      h|help
      v|version
      install
      install-command
      ignore
      nolink|no-link
      regenerate
    )
);

if ( $opt{h} ) {
    print <<EOF;
Install all dependencies of a JS project using Debian dependencies when
available.

Options:
 -h, --help: print this
 --install: launch install-command if some Debian packages are missing
 --ignore: ignore missing Debian packages
 --install-command: command to install mising packages. Default:
                    "--install-command 'sudo apt install'"
 --no-link: don't link JS modules from Debian directories
 --regenerate: force package-lock.json regeneration
EOF
    exit;
}
elsif ( $opt{v} ) {
    print "$VERSION\n";
    exit;
}

$opt{'install-command'} ||= 'sudo apt install';

# Step 0: generate package-lock.json if needed

if ( $opt{regenerate} or not -e 'package-lock.json' ) {
    spawn(
        exec       => [qw(npm i --package-lock-only --legacy-peer-deps)],
        wait_child => 1
    );
}

# Step 1: read package-lock.json and dispatch modules into lists:
#          - Debian packages to install
#          - Debian JS modules to link
#          - JS modules to download

my $content;
{
    open my $f, 'package-lock.json' or die $!;
    local $/ = undef;
    $content = JSON::from_json(<$f>);
    close $f;
}

unless ( $content->{packages} ) {
    die 'Unable to fine "packages" key in package-lock.json';
}

my ( %toLink, %toInstall, %toDownload, %maybeToDownload );

M: foreach my $package (
    sort {
        my @_a = ( $a =~ m#(node_modules/)#g );
        my @_b = ( $b =~ m#(node_modules/)#g );
        @_a <=> @_b || $a cmp $b;
    } keys %{ $content->{packages} }
  )
{
    next unless $package;                            # Skip "" key
    next unless $content->{packages}->{$package};    # Skip deleted
    my $module = $package;
    while ( $module =~ s#.*?node_modules/## ) {
        if ( $module =~ m#(.*?)/node_modules/# ) {
            next M if $toLink{$1};
        }
    }
    $module =~ s#.*node_modules/##;
    if ( my $debianPackage = availableModules->{$module} ) {
        $toLink{$module}++;
        unless ( installedModules->{$module} ) {
            push @{ $toInstall{$debianPackage} }, $module;
            $maybeToDownload{$module} =
              $content->{packages}->{$package}->{resolved};
        }
        delete $content->{packages}->{$package};
        delete $content->{dependencies}->{$module};
    }
    else {
        $toDownload{$package} = $content->{packages}->{$package}->{resolved};
    }
}

# Summary
print scalar(%toLink)
  . ' modules '
  . ( $opt{nolink} ? 'already availables' : 'to link' ) . "\n";
print scalar(%toDownload) . " modules to download\n";

# Step 2: download missing Debian packages if needed

if (%toInstall) {
    unless ( $opt{install} or $opt{ignore} ) {
        print STDERR "\nThe following packages are needed, choose one of "
          . "--ignore or --install\n"
          . join( ' ', sort keys %toInstall ) . "\n";
        exit 1;
    }
    if ( $opt{install} ) {
        print scalar(%toInstall) . " packages to install\n";
        spawn(
            exec => [
                'sh',
                '-c',
                $opt{'install-command'} . ' '
                  . join( ' ', sort keys %toInstall )
            ],
            wait_child => 1,
        );
    }
}

mkdir 'node_modules';

# Step 3: link Debian JS modules into node_modules if needed

unless ( $opt{nolink} ) {

    # Reset cache
    installedModules(1);
    foreach my $module ( keys %toLink ) {
        if ( installedModules->{$module} ) {
            if ( $module =~ m#(.*)/# ) {
                mkdir "node_modules/$1";
            }
            spawn(
                exec       => [ 'rm', '-rf', "node_modules/$module" ],
                wait_child => 1
            );
            spawn(
                exec => [
                    'ln',                        '-s',
                    installedModules->{$module}, "node_modules/$module"
                ],
                wait_child => 1
            );
        }
    }
}

# Step 4: download missing

$progress->target( scalar %toDownload );

if (%toDownload) {
    print "Downloading...\n";
    foreach my $modulePath ( keys %toDownload ) {
        $progress->update( message => $modulePath );
        downloadAndInstall( $modulePath, $toDownload{$modulePath} );
    }
    print "Done\n";
}
