The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package # hide from PAUSE
App::YTDL::Options;

use warnings;
use strict;
use 5.010000;

use Exporter qw( import );
our @EXPORT_OK = qw( read_config_file set_options );

use File::Spec::Functions qw( catfile );
use File::Temp            qw();
use FindBin               qw( $RealBin $RealScript );
use Pod::Usage            qw( pod2usage );

use Term::Choose           qw( choose );
use Term::Choose::Util     qw( print_hash choose_a_dir choose_a_number settings_menu insert_sep );
use Term::ReadLine::Simple qw();

use if $^O eq 'MSWin32', 'Win32::Console::ANSI';

use App::YTDL::ChooseVideos qw( set_sort_videolist );
use App::YTDL::Helper       qw( write_json read_json uni_capture HIDE_CURSOR SHOW_CURSOR );



sub _show_info {
    my ( $opt ) = @_;
    my ( $youtube_dl_version, $ffmpeg_version, $ffprobe_version );
    if ( ! eval { $youtube_dl_version = uni_capture( $opt->{youtube_dl}, '--version' ); 1 } ) {
        $youtube_dl_version = $@;
    }
    eval {
        $ffmpeg_version = uni_capture( $opt->{ffmpeg}, '-version' );
        if ( $ffmpeg_version && $ffmpeg_version =~ /^ffmpeg version (\S+)/m ) {
            $ffmpeg_version = $1;
        }
        else {
            $ffmpeg_version = '';
        }
    };
    eval {
        $ffprobe_version = uni_capture( $opt->{ffprobe}, '-version' );
        if ( $ffprobe_version && $ffprobe_version =~ /^ffprobe version (\S+)/m ) {
            $ffprobe_version = $1;
        }
        else {
            $ffprobe_version = '';
        }
    };
    my $version     = '  version  ';
    my $bin         = '    bin    ';
    my $video_dir   = ' video dir ';
    my $config_dir  = 'config dir ';
    my $archive_dir = 'archive dir';
    my $youtube_dl  = 'youtube-dl ';
    my $ffmpeg      = '  ffmpeg   ';
    my $ffprobe     = '  ffprobe  ';
    my $path = {
        $version     => $main::VERSION,
        $bin         => catfile( $RealBin, $RealScript ),
        $video_dir   => $opt->{video_dir},
        $config_dir  => $opt->{config_dir},
        $archive_dir => $opt->{archive_dir},
        $youtube_dl  => $youtube_dl_version // 'error',
        $ffmpeg      => $ffmpeg_version,
        $ffprobe     => $ffprobe_version,
    };
    my $keys = [ $bin, $version, $video_dir, $config_dir, $archive_dir, $youtube_dl ];
    push @$keys, $ffmpeg  if $ffmpeg_version;
    push @$keys, $ffprobe if $ffprobe_version;
    print_hash( $path, { keys => $keys, preface => ' Close with ENTER' } );
}


sub _menus {
    my $menus = {
        main => [
            [ 'show_help_text',   "  HELP"       ],
            [ 'show_info',        "  INFO"       ],
            [ 'group_directory',  "- Directory"  ],
            [ 'group_file',       "- File"       ],
            [ 'group_quality',    "- Quality"    ],
            [ 'group_download',   "- Download"   ],
            [ 'group_merge',      "- Merge"      ],
            [ 'group_history',    "- History"    ],
            [ 'group_list_menu',  "- Video List" ],
            [ 'group_output',     "- Output"     ],
            [ 'group_youtube_dl', "- Youtube-dl" ],
        ],
        group_directory => [
            [ 'video_dir',     "- Main video directory" ],
            [ 'extractor_dir', "- Extractor directory"  ],
            [ 'uploader_dir',  "- Uploader directory"   ],
        ],
        group_file => [
            [ 'max_len_f_name',      "- Max filename length" ],
            [ 'replace_spaces',      "- Replace spaces"      ],
            [ 'sanitize_filename',   "- Sanitize filename"   ],
            [ 'unmappable_filename', "- Encode filename"     ],
            [ 'modify_timestamp',    "- File timestamp"      ],
        ],
        group_quality => [
            [ 'auto_quality',     "- Set auto-quality mode"    ],
            [ 'pref_qual_slots',  "- Number of slots"          ],
            [ '_print_pref_qual', "  Preferred qualities" ],
        ],
        group_download => [
            [ 'useragent',            "- UserAgent"            ],
            [ 'skip_archived_videos', "- Skip archived videos" ],
            [ 'overwrite',            "- Overwrite"            ],
            [ 'retries',              "- Download retries"     ],
            [ 'timeout',              "- Timeout"              ],
        ],
        group_merge => [
            [ 'merge_enabled',   "- Enable Merge"      ],
            [ 'merge_overwrite', "- ffmpeg overwrites" ],
            [ 'merge_ext',       "- Output formats"    ],
            [ 'merge_loglevel',  "- Verbosity"         ],
            [ 'merged_in_files', "- Input files"       ],
        ],
        group_history => [
            [ 'max_size_history',          "- Size history"     ],
            [ 'sort_history_by_timestamp', "- History sort"     ],
            [ 'enable_download_archive',   "- Download archive" ],
        ],

        group_list_menu => [
            [ '_fast_listmenu',  "- Fast list-menu"  ],
            [ 'small_list_size', "- List size"       ],
            [ 'list_sort_item',  "- Sort order"      ],
            [ 'show_view_count', "- Show view count" ],
        ],
        group_output => [
            [ 'text_unidecode', "- Unmappable infotext" ],
            [ 'max_info_width', "- Max info width"    ],
        ],
        group_youtube_dl => [
            [ 'use_netrc',         "- Use '.netrc'" ],
            [ '_avail_extractors', "  Extractors"        ],
        ],
    };
    return $menus;
}


sub _sub_menus {
    my ( $key ) = @_;
    my $sub_menus = {
        _fast_listmenu => [
            [ 'fast_list_vimeo',   "- vimeo",   [ 'NO', 'YES' ] ],
            [ 'fast_list_youtube', "- youtube", [ 'NO', 'YES' ] ],
        ],
    };
    return $sub_menus->{$key} if $key;
    return $sub_menus;
}


sub _group_prompt {
    my ( $group ) = @_;
    my $group_prompt ={
        group_directory => "Directories:",
        group_file      => "Files:",
        group_quality   => "Quality:",
        group_download  => "Download:",
        group_merge     => "Merge:",
        group_history   => "History:",
        group_list_menu => "List menu:",
        group_output    => "Appearance:",
    };
    return $group_prompt->{$group};
}


sub set_options {
    my ( $opt ) = @_;
    my $menus     = _menus();
    my $sub_menus = _sub_menus();
    my @keys;
    for my $group ( keys %$menus ) {
        next if $group eq 'main';
        push @keys, grep { ! /^_/ } map { $_->[0] } @{$menus->{$group}};
    }
    for my $key ( %$sub_menus ) {
        push @keys, map { $_->[0] } @{$sub_menus->{$key}};
    }
    my $group = 'main';
    my $backup_old_idx = 0;
    my $old_idx = 0;

    GROUP: while ( 1 ) {
        my $menu = $menus->{$group};

        OPTION: while ( 1 ) {
            my $back     = $group eq 'main' ? '  QUIT' : '  <<';
            my $continue = '  CONTINUE';
            my @pre  = ( undef, $group eq 'main' ? $continue : () );
            my @real = map( $_->[1], @$menu );
            my $prompt = _group_prompt( $group ) // 'Options:';
            # Choose
            my $idx = choose(
                [ @pre, @real ],
                { prompt => $prompt, layout => 3, index => 1, clear_screen => 1, undef => $back, default => $old_idx }
            );
            if ( ! $idx ) {
                if ( $group eq 'main' ) {
                    _write_config_file( $opt, $opt->{config_file}, @keys );
                    exit;
                }
                else {
                    $old_idx = $backup_old_idx;
                    $group = 'main';
                    next GROUP;
                }
            }
            if ( $old_idx == $idx ) {
                $old_idx = 0;
                next OPTION;
            }
            $old_idx = $idx;
            my $choice = $idx <= $#pre ? $pre[$idx] : $menu->[$idx - @pre][0];
            if ( $choice =~ /^group_/ ) {
                $backup_old_idx = $old_idx;
                $old_idx = 0;
                $group = $choice;
                redo GROUP;
            }
            if ( $choice eq $continue ) {
                if ( $group =~ /^group_/ ) {
                    $group = 'main';
                    redo GROUP;
                }
                _write_config_file( $opt, $opt->{config_file}, @keys );
                last GROUP;
            }
            if ( $choice eq "show_help_text" ) {
                pod2usage( { -exitval => 'NOEXIT', -verbose => 2 } );
            }
            elsif ( $choice eq "show_info" ) {
                _show_info( $opt );
            }
            elsif ( $choice eq "_avail_extractors" ) {
                print HIDE_CURSOR;
                say 'Close with ENTER';
                my @capture;
                if ( ! eval { @capture = uni_capture( $opt->{youtube_dl}, '--list-extractors' ); 1 } ) {
                    say "List extractors: $@";
                }
                print SHOW_CURSOR;
                if ( @capture ) {
                    choose(
                        [ map { "  $_" } @capture ],
                        { layout => 3 }
                    );
                }
            }
            elsif ( $choice eq "video_dir" ) {
                my $prompt = 'Video directory';
                _opt_choose_a_directory( $opt, $choice, $prompt );
            }
            elsif ( $choice eq "extractor_dir" ) {
                my $prompt = 'Use extractor directories';
                my $list = [ qw( no yes ) ];
                _opt_choose_from_list_idx( $opt, $choice, $prompt, $list );
            }
            elsif ( $choice eq "uploader_dir" ) {
                my $prompt = 'Use uploader directories';
                my $list = [
                    'no',
                    'if from list-menu', #
                    'always',
                ];
                _opt_choose_from_list_idx( $opt, $choice, $prompt, $list );
            }
            elsif ( $choice eq "max_len_f_name" ) {
                my $prompt = 'Max filename length';
                my $digits = 3;
                _opt_number_range( $opt, $choice, $prompt, $digits );
            }
            elsif ( $choice eq "replace_spaces" ) {
                my $prompt = 'Replace spaces in filenames';
                my $list = [ qw( no yes ) ];
                _opt_choose_from_list_idx( $opt, $choice, $prompt, $list );
            }
            elsif ( $choice eq "sanitize_filename" ) {
                my $prompt = 'Sanitize filenames';
                my $list = [
                    'keep all',
                    'replace  / \ : " * ? < > | &',
                    'replace  /',
                ];
                _opt_choose_from_list_idx( $opt, $choice, $prompt, $list );
            }
            elsif ( $choice eq "unmappable_filename" ) {
                my $prompt = 'Filename not mappable to file system encoding';
                my $list = [
                    'don\'t encode',
                    'use Text::Unidecode',
                ];
                _opt_choose_from_list_idx( $opt, $choice, $prompt, $list );
            }
            elsif ( $choice eq "modify_timestamp" ) {
                my $prompt = 'Timestamp to upload date';
                my $list = [ qw( no yes ) ];
                _opt_choose_from_list_idx( $opt, $choice, $prompt, $list );
            }
            elsif ( $choice eq "auto_quality" ) {
                my $prompt = 'Auto quality';
                my $list = [
                    'manually',
                    'keep_uploader_playlist',
                    'keep_extractor',
                    'preferred',
                    'default',
                ];
                _opt_choose_from_list_value( $opt, $choice, $prompt, $list );
            }
            elsif ( $choice eq "_print_pref_qual" ) {
                my $pref = read_json( $opt, $opt->{preferred_file} ) // {};
                if ( ! %$pref ) {
                    choose( [ 'Close with ENTER' ], { prompt => 'No preferred qualities set.' } );
                }
                else {
                    my @p = ( ' ' );
                    for my $key ( sort keys %$pref ) {
                        push @p, "$key:";
                        my $c = 1;
                        push @p, map { sprintf "%4d. [%s]\n", $c++, join ',', @$_ } @{$pref->{$key}};
                        push @p, ' ';
                    }
                    choose( \@p, { prompt => 'Close with ENTER', layout => 3, justify => 0 } );
                }
            }
            elsif ( $choice eq "pref_qual_slots" ) {
                my $prompt = 'Slots \'preferred qualilties\'';
                my $digits = 2;
                _opt_number_range( $opt, $choice, $prompt, $digits );
            }
            elsif ( $choice eq "useragent" ) {
                my $prompt = 'Set the UserAgent';
                my $list = [
                    [ 'UserAgent', $opt->{useragent} ]
                ];
                _local_readline( $opt, $choice, $prompt, $list );
                $opt->{useragent} = 'Mozilla/5.0' if $opt->{useragent} eq '';
                $opt->{useragent} = ''            if $opt->{useragent} eq '""';
            }
            elsif ( $choice eq "skip_archived_videos" ) {
                my $prompt = 'Skip archived videos';
                my $list = [ qw( no yes ) ];
                _opt_choose_from_list_idx( $opt, $choice, $prompt, $list );
            }
            elsif ( $choice eq "overwrite" ) {
                my $prompt = 'Overwrite files';
                my $list = [
                    'no (append)',
                    'yes'
                ];
                _opt_choose_from_list_idx( $opt, $choice, $prompt, $list );
            }
            elsif ( $choice eq "retries" ) {
                my $prompt = 'Download retries';
                my $digits = 3;
                _opt_number_range( $opt, $choice, $prompt, $digits );
            }
            elsif ( $choice eq "timeout" ) {
                my $prompt = 'Connection timeout (s)';
                my $digits = 3;
                _opt_number_range( $opt, $choice, $prompt, $digits );
            }
            elsif ( $choice eq "merge_enabled" ) {
                my $prompt = 'Enable merge';
                my $list = [ qw( no ask yes ) ];
                _opt_choose_from_list_idx( $opt, $choice, $prompt, $list );
            }
            elsif ( $choice eq "merge_overwrite" ) {
                my $prompt = '\'ffmpeg\' - overwrite files';
                my $list = [ qw( ask yes ) ];
                _opt_choose_from_list_idx( $opt, $choice, $prompt, $list );
            }
            elsif ( $choice eq "merge_ext" ) {
                my $prompt = 'Output format of merged files';
                my $list = [ qw( mkv mp4 ogg webm flv ) ];
                _opt_choose_from_list_value( $opt, $choice, $prompt, $list );
            }
            elsif ( $choice eq "merge_loglevel" ) {
                my $prompt = '\'ffmpeg\' verbosity';
                my $list = [ qw( error warning info ) ];
                _opt_choose_from_list_value( $opt, $choice, $prompt, $list );
            }
            elsif ( $choice eq "merged_in_files" ) {
                my $prompt = 'The merged input files:';
                my $list = [
                    'keep',
                    'move to "' . $opt->{dir_stream_files} . '"',
                    'remove'
                ];
                _opt_choose_from_list_idx( $opt, $choice, $prompt, $list );
            }
            elsif ( $choice eq "max_size_history" ) {
                my $prompt = 'Max size of history';
                my $digits = 3;
                _opt_number_range( $opt, $choice, $prompt, $digits );
            }
            elsif ( $choice eq "sort_history_by_timestamp" ) {
                my $prompt = 'Sort history by';
                my $list = [
                    'by name',
                    'by timestamp'
                ];
                _opt_choose_from_list_idx( $opt, $choice, $prompt, $list );
            }
            elsif ( $choice eq "enable_download_archive" ) {
                my $prompt = 'Enable downlaod archive';
                my $list = [ qw( no yes more ) ];
                _opt_choose_from_list_idx( $opt, $choice, $prompt, $list );
            }
            elsif ( $choice eq "_fast_listmenu" ) {
                my $sub_menu = _sub_menus( $choice );
                my $current = { map { $_->[0] => $opt->{$_->[0]} } @$sub_menu };
                my $prompt = 'Enable fast list-menu for';
                _opt_settings_menu( $opt, $sub_menu, $current, $prompt );
            }
            elsif ( $choice eq "small_list_size" ) {
                my $prompt = 'Videos in list-menu';
                my $list = [
                    'all',
                    'latest',
                ];
                _opt_choose_from_list_idx( $opt, $choice, $prompt, $list );
            }
            elsif ( $choice eq "list_sort_item" ) {
                set_sort_videolist( $opt );
            }
            elsif ( $choice eq "show_view_count" ) {
                my $prompt = 'Show view count in list-menus';
                my $list = [
                    'if sorted by "view count"',
                    'always',
                ];
                _opt_choose_from_list_idx( $opt, $choice, $prompt, $list );
            }
            elsif ( $choice eq "text_unidecode" ) {
                my $prompt = 'Characters not mappable to console out';
                my $list = [
                    'replace with *',
                    'use Text::Unidecode',
                ];
                _opt_choose_from_list_idx( $opt, $choice, $prompt, $list );
            }
            elsif ( $choice eq "max_info_width" ) {
                my $prompt = 'Max Info width';
                my $digits = 3;
                _opt_number_range( $opt, $choice, $prompt, $digits );
            }
            elsif ( $choice eq "use_netrc" ) {
                my $prompt = 'Enable the option --netrc for youtube-dl';
                my $list = [ qw( no yes ) ];
                _opt_choose_from_list_idx( $opt, $choice, $prompt, $list );
            }
            else { die $choice }
        }
    }
    return;
}


sub _opt_settings_menu {
    my ( $opt, $sub_menu, $current, $prompt ) = @_;
    my $changed = settings_menu( $sub_menu, $current, { prompt => $prompt, in_place => 0 } );
    return if ! $changed;
    for my $key ( keys %$changed ) {
        $opt->{$key} = $changed->{$key};
    }
    $opt->{change}++;
}


sub _opt_choose_a_directory {
    my( $opt, $choice, $prompt ) = @_;
    my $new_dir = choose_a_dir( { dir => $opt->{$choice} } );
    return if ! defined $new_dir;
    if ( $new_dir ne $opt->{$choice} ) {
        if ( ! eval {
            my $fh = File::Temp->new( TEMPLATE => 'XXXXXXXXXXXXXXX', UNLINK => 1, DIR => $new_dir );
            1 }
        ) {
            print "$@";
            choose( [ 'Press Enter:' ], { prompt => '' } );
        }
        else {
            $opt->{$choice} = $new_dir;
            $opt->{change}++;
        }
    }
}


sub _local_readline {
    my ( $opt, $section, $prompt, $list ) = @_;
    my $trs = Term::ReadLine::Simple->new();
    my $new_list = $trs->fill_form(
        $list,
        { prompt => $prompt, auto_up => 2, confirm => '  CONFIRM', back => '  BACK' }
    );
    if ( $new_list ) {
        for my $i ( 0 .. $#$new_list ) {
            $opt->{$section} = $new_list->[$i][1];
        }
        $opt->{change}++;
    }
}


sub _opt_number_range {
    my ( $opt, $section, $prompt, $digits ) = @_;
    my $current = $opt->{$section};
    $current = insert_sep( $current );
    # Choose_a_number
    my $choice = choose_a_number( $digits, { name => "\"$prompt\"", current => $current } );
    return if ! defined $choice;
    $opt->{$section} = $choice eq '--' ? undef : $choice;
    $opt->{change}++;
    return;
}


sub _opt_choose_from_list_idx {
    my ( $opt, $section, $prompt, $list ) = @_;
    my $backup = $opt->{$section};
    my $confirm = '  CONFIRM';
    my @pre = ( undef, $confirm );
    my $choices = [ @pre, map { "- $_" } @$list ];
    while ( 1 ) {
        my $local_prompt = $prompt . ': [' . ( $list->[$opt->{$section}] // '--' ) . ']';
        my $idx = choose(
            $choices,
            { prompt => $local_prompt, layout => 3, undef => '  BACK', index => 1 }
        );
        if ( ! defined $idx || $idx == 0 ) {
            $opt->{$section} = $backup;
            return;
        }
        elsif ( $choices->[$idx] eq $confirm ) {
            $opt->{change}++;
            return;
        }
        else {
            $opt->{$section} = $idx - @pre;
        }
    }
}


sub _opt_choose_from_list_value {
    my ( $opt, $section, $prompt, $list ) = @_;
    my $backup = $opt->{$section};
    my $confirm = '  CONFIRM';
    my @pre = ( undef, $confirm );
    while ( 1 ) {
        my $local_prompt = $prompt . ': [' . ( $opt->{$section} // '--' ) . ']';
        my $choice = choose(
            [ @pre,  map { "- $_" } @$list ],
            { prompt => $local_prompt, layout => 3, undef => '  BACK', index => 0 }
        );
        if ( ! defined $choice ) {
            $opt->{$section} = $backup;
            return;
        }
        elsif ( $choice eq $confirm ) {
            $opt->{change}++;
            return;
        }
        else {
            $choice =~ s/^-\s//;
            $opt->{$section} = $choice;
        }
    }
}


sub _write_config_file {
    my ( $opt, $file, @keys ) = @_;
    return if ! $opt->{change};
    my $tmp = {};
    for my $section ( sort @keys ) {
        $tmp->{$section} = $opt->{$section};
    }
    write_json( $opt, $file, $tmp );
    delete $opt->{change};
}


sub read_config_file {
    my ( $opt, $file ) = @_;
    my $tmp = read_json( $opt, $file ) // {};
    my @keys = keys %$tmp;
    for my $section ( @keys ) {
        $opt->{$section} = $tmp->{$section};
    }
    return @keys;
}


1;


__END__