package App::Codeowners::Options;
# ABSTRACT: Getopt and shell completion for App::Codeowners

use warnings;
use strict;

use Getopt::Long 2.39 ();
use Path::Tiny;
use Pod::Usage;

our $VERSION = '0.43'; # VERSION

sub early_options {
    return {
        'color|colour!'         => (-t STDOUT ? 1 : 0), ## no critic (InputOutput::ProhibitInteractiveTest)
        'format|f=s'            => undef,
        'help|h|?'              => 0,
        'manual|man'            => 0,
        'shell-completion:s'    => undef,
        'version|v'             => 0,
    };
}

sub command_options {
    return {
        'create'    => {},
        'owners'    => {
            'pattern=s' => '',
        },
        'patterns'  => {
            'owner=s'   => '',
        },
        'projects'  => {},
        'show'      => {
            'owner=s@'      => [],
            'pattern=s@'    => [],
            'project=s@'    => [],
            'patterns!'     => 0,
            'projects!'     => undef,
        },
        'update'    => {},
    };
}

sub commands {
    my $self = shift;
    my @commands = sort keys %{$self->command_options};
    return @commands;
}

sub options {
    my $self = shift;
    my @command_options;
    if (my $command = $self->{command}) {
        @command_options = keys %{$self->command_options->{$command} || {}};
    }
    return (keys %{$self->early_options}, @command_options);
}

sub new {
    my $class = shift;
    my @args  = @_;

    my $self = bless {}, $class;

    my @args_copy = @args;

    my $opts = $self->get_options(
        args    => \@args,
        spec    => $self->early_options,
        config  => 'pass_through',
    ) or pod2usage(2);

    if ($ENV{CODEOWNERS_COMPLETIONS}) {
        $self->{command} = $args[0] || '';
        my $cword = $ENV{CWORD};
        my $cur   = $ENV{CUR} || '';
        # Adjust cword to remove progname
        while (0 < --$cword) {
            last if $cur eq ($args_copy[$cword] || '');
        }
        $self->completions($cword, @args_copy);
        exit 0;
    }

    if ($opts->{version}) {
        my $progname = path($0)->basename;
        print "${progname} ${VERSION}\n";
        exit 0;
    }
    if ($opts->{help}) {
        pod2usage(-exitval => 0, -verbose => 99, -sections => [qw(NAME SYNOPSIS OPTIONS COMMANDS)]);
    }
    if ($opts->{manual}) {
        pod2usage(-exitval => 0, -verbose => 2);
    }
    if (defined $opts->{shell_completion}) {
        $self->shell_completion($opts->{shell_completion});
        exit 0;
    }

    # figure out the command (or default to "show")
    my $command = shift @args;
    my $command_options = $self->command_options->{$command || ''};
    if (!$command_options) {
        unshift @args, $command if defined $command;
        $command = 'show';
        $command_options = $self->command_options->{$command};
    }

    my $more_opts = $self->get_options(
        args    => \@args,
        spec    => $command_options,
    ) or pod2usage(2);

    %$self = (%$opts, %$more_opts, command => $command, args => \@args);
    return $self;
}

sub command {
    my $self = shift;
    my $command = $self->{command};
    my @commands = sort keys %{$self->command_options};
    return if not grep { $_ eq $command } @commands;
    $command =~ s/[^a-z]/_/g;
    return $command;
}

sub args {
    my $self = shift;
    return @{$self->{args} || []};
}


sub get_options {
    my $self = shift;
    my $args = {@_ == 1 && ref $_[0] eq 'HASH' ? %{$_[0]} : @_};

    my %options;
    my %results;
    while (my ($opt, $default_value) = each %{$args->{spec}}) {
        my ($name) = $opt =~ /^([^=:!|]+)/;
        $name =~ s/-/_/g;
        $results{$name} = $default_value;
        $options{$opt}  = \$results{$name};
    }

    if (my $fn = $args->{callback}) {
        $options{'<>'} = sub {
            my $arg = shift;
            $fn->($arg, \%results);
        };
    }

    my $p = Getopt::Long::Parser->new;
    $p->configure($args->{config} || 'default');
    return if !$p->getoptionsfromarray($args->{args}, %options);

    return \%results;
}


sub shell_completion {
    my $self = shift;
    my $type = lc(shift || 'bash');

    if ($type eq 'bash') {
    print <<'END';
# git-codeowners - Bash completion
# To use, eval this code:
#   eval "$(git-codeowners --shell-completion)"
# This will work without the bash-completion package, but handling of colons
# in the completion word will work better with bash-completion installed and
# enabled.
_git_codeowners() {
    local cur words cword
    if declare -f _get_comp_words_by_ref >/dev/null
    then
        _get_comp_words_by_ref -n : cur cword words
    else
        words=("${COMP_WORDS[@]}")
        cword=${COMP_CWORD}
        cur=${words[cword]}
    fi
    local IFS=$'\n'
    COMPREPLY=($(CODEOWNERS_COMPLETIONS=1 CWORD="$cword" CUR="$cur" ${words[@]}))
    # COMPREPLY=($(${words[0]} --completions "$cword" "${words[@]}"))
    if [[ "$?" -eq 9 ]]
    then
        COMPREPLY=($(compgen -A "${COMPREPLY[0]}" -- "$cur"))
    fi
    declare -f __ltrim_colon_completions >/dev/null && \
        __ltrim_colon_completions "$cur"
    return 0
}
complete -F _git_codeowners git-codeowners
END
    }
    else {
        # TODO - Would be nice to support Zsh
        warn "No such shell completion: $type\n";
    }
}


sub completions {
    my $self    = shift;
    my $cword   = shift;
    my @words   = @_;

    my $current = $words[$cword]     || '';
    my $prev    = $words[$cword - 1] || '';

    my $reply;

    if ($prev eq '--format' || $prev eq '-f') {
        $reply = $self->_completion_formats;
    }
    elsif ($current =~ /^-/) {
        $reply = $self->_completion_options;
    }
    else {
        if (!$self->command) {
            $reply = [$self->commands, @{$self->_completion_options([keys %{$self->early_options}])}];
        }
        else {
            print 'file';
            exit 9;
        }
    }

    local $, = "\n";
    print grep { /^\Q$current\E/ } @$reply;
    exit 0;
}

sub _completion_options {
    my $self = shift;
    my $opts = shift || [$self->options];

    my @options;

    for my $option (@$opts) {
        my ($names, $op, $vtype) = $option =~ /^([^=:!]+)([=:!]?)(.*)$/;
        my @names = split(/\|/, $names);

        for my $name (@names) {
            if ($op eq '!') {
                push @options, "--$name", "--no-$name";
            }
            else {
                if (length($name) > 1) {
                    push @options, "--$name";
                }
                else {
                    push @options, "-$name";
                }
            }
        }
    }

    return [sort @options];
}

sub _completion_formats { [qw(csv json json:pretty tsv yaml)] }

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

App::Codeowners::Options - Getopt and shell completion for App::Codeowners

=head1 VERSION

version 0.43

=head1 METHODS

=head2 get_options

    $options = $options->get_options(
        args     => \@ARGV,
        spec     => \@expected_options,
        callback => sub { my ($arg, $results) = @_; ... },
    );

Convert command-line arguments to options, based on specified rules.

Returns a hashref of options or C<undef> if an error occurred.

=over 4

=item *

C<args> - Arguments from the caller (e.g. C<@ARGV>).

=item *

C<spec> - List of L<Getopt::Long> compatible option strings.

=item *

C<callback> - Optional coderef to call for non-option arguments.

=item *

C<config> - Optional L<Getopt::Long> configuration string.

=back

=head2 shell_completion

    $options->shell_completion($shell_type);

Print shell code to C<STDOUT> for the given type of shell. When eval'd, the shell code enables
completion for the F<git-codeowners> command.

=head2 completions

    $options->completions($current_arg_index, @args);

Print completions to C<STDOUT> for the given argument list and cursor position, and exit.

May also exit with status 9 and a compgen action printed to C<STDOUT> to indicate that the shell
should generate its own completions.

Doesn't return.

=head1 BUGS

Please report any bugs or feature requests on the bugtracker website
L<https://github.com/chazmcgarvey/git-codeowners/issues>

When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.

=head1 AUTHOR

Charles McGarvey <chazmcgarvey@brokenzipper.com>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2019 by Charles McGarvey.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
