#!/usr/bin/env perl
# Copyright SUSE LLC
# SPDX-License-Identifier: GPL-2.0-or-later
# Generate bash and zsh completion scripts for openqa-cli from openqa-cli.yaml.
# Run this script after adding or changing options in public/openqa-cli.yaml.
use strict;
use warnings;
use YAML::PP;
use Mojo::File qw(path);
use feature 'signatures';

my $spec_file = 'public/openqa-cli.yaml';
my $bash_out = 'contrib/completions/openqa-cli-completion.bash';
my $zsh_out = 'contrib/completions/openqa-cli-completion.zsh';

my $spec = YAML::PP->new->load_file($spec_file);

sub parse_option ($entry) {
    if (ref $entry eq 'HASH') {
        my $name = $entry->{name};
        my $desc = $entry->{summary} // '';
        $desc =~ s/\n\s*/ /g;
        $desc =~ s/\s+$//;
        my $has_arg = defined $entry->{type} ? 1 : 0;
        return ($name, $desc, $has_arg);
    }
    # String format: "name[|alias][=type][@]   --description"
    my ($spec_part, $desc) = split /\s+--/, $entry, 2;
    my ($name_part, $type) = split /=/, $spec_part;
    my ($name) = split /\|/, $name_part;
    $name =~ s/^\s+|\s+$//g;
    $desc //= '';
    $desc =~ s/\n\s*/ /g;
    $desc =~ s/\s+$//;
    return ($name, $desc, defined $type ? 1 : 0);
}

sub collect_options ($option_list) {
    return map { [parse_option($_)] } @{$option_list // []};
}

my @global_opts = collect_options($spec->{options});
my %subcommands = %{$spec->{subcommands} // {}};
my @subcmds = sort keys %subcommands;

# bash

sub bash_option_list (@opts) {
    join ' ', map { "--$_->[0]" } @opts;
}

my $bash_content = <<'HEADER';
# Generated by tools/generate-cli-completions — do not edit manually.
# Regenerate with: make generate-completions
_openqa_cli_completions() {
    local cur subcommands main_options api_options archive_options monitor_options schedule_options
    cur="${COMP_WORDS[COMP_CWORD]}"
HEADER

$bash_content .= sprintf "    subcommands=\"%s\"\n", join ' ', @subcmds;
$bash_content .= sprintf "    main_options=\"%s\"\n", bash_option_list(@global_opts);

for my $cmd (@subcmds) {
    my @opts = collect_options($subcommands{$cmd}{options});
    $bash_content .= sprintf "    %s_options=\"%s\"\n", $cmd, bash_option_list(@opts);
}

$bash_content .= <<'BODY';

    if [[ ${COMP_CWORD} -eq 1 ]]; then
        COMPREPLY=($(compgen -W "$subcommands" -- "$cur"))
        return 0
    fi
    case "${COMP_WORDS[1]}" in
BODY

for my $cmd (@subcmds) {
    $bash_content .= sprintf "        %s)\n", $cmd;
    $bash_content .= sprintf "            COMPREPLY=(\$(compgen -W \"\$main_options \$%s_options\" -- \"\$cur\"))\n",
      $cmd;
    $bash_content .= "            ;;\n";
}

$bash_content .= <<'FOOTER';
        *)
            COMPREPLY=($(compgen -W "$subcommands" -- "$cur"))
            ;;
    esac
}

complete -F _openqa_cli_completions openqa-cli
FOOTER

path($bash_out)->spew($bash_content);

# zsh

sub zsh_option_entry ($name, $desc, $has_arg = 0) {
    $desc =~ s/\\/\\\\/g;    # backslash first
    $desc =~ s/"/\\"/g;    # double quotes
    $desc =~ s/\$/\\\$/g;    # dollar signs
    $desc =~ s/:/\\:/g;    # colon (word:desc separator for _describe)
    my $suffix = $has_arg ? '=' : '';
    return sprintf '        "--%s%s:%s"', $name, $suffix, $desc;
}

my $zsh_content = <<'HEADER';
#compdef openqa-cli
# Generated by tools/generate-cli-completions — do not edit manually.
# Regenerate with: make generate-completions

_openqa_cli_completions() {
    local curcontext="$curcontext"
    local -a subcommands main_options
HEADER

for my $cmd (@subcmds) {
    $zsh_content .= sprintf "    local -a %s_options\n", $cmd;
}

$zsh_content .= sprintf "    subcommands=(%s)\n", join ' ', map { "\"$_\"" } @subcmds;

$zsh_content .= "    main_options=(\n";
$zsh_content .= join("\n", map { zsh_option_entry(@$_) } @global_opts) . "\n";
$zsh_content .= "    )\n";

for my $cmd (@subcmds) {
    my @opts = collect_options($subcommands{$cmd}{options});
    $zsh_content .= sprintf "    %s_options=(\n", $cmd;
    $zsh_content .= join("\n", map { zsh_option_entry(@$_) } @opts) . "\n";
    $zsh_content .= "    )\n";
}

$zsh_content .= <<'BODY';
    if (( CURRENT == 2 )); then
        _describe 'subcommand' subcommands
    else
        case "$words[2]" in
BODY

for my $cmd (@subcmds) {
    $zsh_content .= sprintf "            %s)\n", $cmd;
    $zsh_content .= sprintf "                local -a _all\n";
    $zsh_content .= sprintf "                _all=(\"\${main_options[@]}\" \"\${%s_options[@]}\")\n", $cmd;
    $zsh_content .= sprintf "                _describe 'option' _all\n";
    $zsh_content .= "                ;;\n";
}

$zsh_content .= <<'FOOTER';
            *)
                _values 'subcommand' api archive monitor schedule
                ;;
        esac
    fi
}
FOOTER

path($zsh_out)->spew($zsh_content);

printf "Generated:\n  %s\n  %s\n", $bash_out, $zsh_out;
