#!/usr/bin/env perl
# script/generate-commands
#
# Generates Async::Redis::Commands from redis-doc commands.json

use strict;
use warnings;
use 5.018;
use Getopt::Long;
use JSON::PP;
use File::Basename;

my ($input, $output, $help);
GetOptions(
    'input=s'  => \$input,
    'output=s' => \$output,
    'help'     => \$help,
) or usage();
usage() if $help;

$input  //= 'script/commands.json';
$output //= 'lib/Async/Redis/Commands.pm';

die "Input file not found: $input\n" unless -f $input;

# Load commands.json
open my $fh, '<', $input or die "Cannot open $input: $!";
my $json = do { local $/; <$fh> };
close $fh;

my $commands = decode_json($json);

# Generate module
my $code = generate_module($commands);

# Write output
my $dir = dirname($output);
system("mkdir", "-p", $dir) if $dir && !-d $dir;

open my $out, '>', $output or die "Cannot write $output: $!";
print $out $code;
close $out;

say "Generated $output with " . scalar(keys %$commands) . " commands";
exit 0;

sub usage {
    say "Usage: $0 [--input FILE] [--output FILE]";
    say "  --input   Path to commands.json (default: script/commands.json)";
    say "  --output  Output module path (default: lib/Async/Redis/Commands.pm)";
    exit 1;
}

sub generate_module {
    my ($commands) = @_;

    my @methods;
    my %seen;

    for my $cmd (sort keys %$commands) {
        my $info = $commands->{$cmd};
        my $method = cmd_to_method($cmd);

        next if $seen{$method}++;

        my $parts = [split /\s+/, $cmd];
        my $is_subcommand = @$parts > 1;

        push @methods, generate_method($method, $parts, $info);
    }

    return <<"END_MODULE";
# lib/Async/Redis/Commands.pm
# AUTO-GENERATED - DO NOT EDIT
# Generated from redis-doc commands.json
#
# This module provides async method wrappers for all Redis commands.
# Consume this role in Async::Redis.

package Async::Redis::Commands;

use strict;
use warnings;
use 5.018;

use Future::AsyncAwait;

@{[ join("\n\n", @methods) ]}

1;

__END__

=head1 NAME

Async::Redis::Commands - Auto-generated Redis command methods

=head1 DESCRIPTION

This module is auto-generated from the Redis command documentation.
It provides async method wrappers for all Redis commands.

Do not edit this file directly. Regenerate with:

  perl script/generate-commands

=cut
END_MODULE
}

sub cmd_to_method {
    my ($cmd) = @_;

    # Replace spaces and hyphens with underscores, lowercase
    my $method = lc($cmd);
    $method =~ s/[\s-]+/_/g;

    return $method;
}

sub generate_method {
    my ($method, $parts, $info) = @_;

    my $cmd_name = uc($parts->[0]);
    my @subcmd = @$parts[1..$#$parts];

    # Check for special transformations
    if ($method eq 'hgetall') {
        return generate_hgetall();
    }
    elsif ($method eq 'info') {
        return generate_info();
    }
    elsif ($method eq 'time') {
        return generate_time();
    }

    # Standard command
    if (@subcmd) {
        my $subcmd_str = join(', ', map { "'$_'" } map { uc } @subcmd);
        return <<"END_METHOD";
async sub $method {
    my \$self = shift;
    return await \$self->command('$cmd_name', $subcmd_str, \@_);
}
END_METHOD
    }
    else {
        return <<"END_METHOD";
async sub $method {
    my \$self = shift;
    return await \$self->command('$cmd_name', \@_);
}
END_METHOD
    }
}

sub generate_hgetall {
    return <<'END_METHOD';
async sub hgetall {
    my $self = shift;
    my $arr = await $self->command('HGETALL', @_);
    return { @$arr };  # Convert array to hash
}
END_METHOD
}

sub generate_info {
    return <<'END_METHOD';
async sub info {
    my $self = shift;
    my $raw = await $self->command('INFO', @_);
    return _parse_info($raw);
}

sub _parse_info {
    my ($raw) = @_;
    return {} unless defined $raw;

    my %info;
    my $section = 'default';

    for my $line (split /\r?\n/, $raw) {
        if ($line =~ /^# (\w+)/) {
            $section = lc($1);
            $info{$section} //= {};
        }
        elsif ($line =~ /^(\w+):(.*)$/) {
            $info{$section}{$1} = $2;
        }
    }

    return \%info;
}
END_METHOD
}

sub generate_time {
    return <<'END_METHOD';
async sub time {
    my $self = shift;
    my $arr = await $self->command('TIME', @_);
    return {
        seconds      => $arr->[0],
        microseconds => $arr->[1],
    };
}
END_METHOD
}
