package Acme::Claude::Shell::Tools;

use 5.020;
use strict;
use warnings;

use Exporter 'import';
our @EXPORT_OK = qw(shell_tools);

use Claude::Agent qw(tool);
use IO::Async::Process;
use Future;
use Cwd qw(abs_path getcwd);

=head1 NAME

Acme::Claude::Shell::Tools - SDK MCP tool definitions for Acme::Claude::Shell

=head1 SYNOPSIS

    use Acme::Claude::Shell::Tools qw(shell_tools);

    my $tools = shell_tools($session);

=head1 DESCRIPTION

Defines the SDK MCP tools that Claude can use to execute shell commands.
All shell operations go through C<execute_command> so the safety hooks
can confirm each command before execution.

=over 4

=item * B<execute_command> - Run shell commands (with user confirmation via hooks)

=item * B<get_working_directory> - Get current working directory (no confirmation needed)

=back

Each tool returns a Future for async execution.

=cut

sub shell_tools {
    my ($session) = @_;

    return [
        # execute_command tool - ALL shell operations go through this
        # so the PreToolUse hook can confirm each command
        tool(
            'execute_command',
            'Execute a shell command and return its output. Use this for ALL shell operations including listing files, reading files, etc. The user will be prompted to approve each command.',
            {
                type       => 'object',
                properties => {
                    command => {
                        type        => 'string',
                        description => 'The shell command to execute (e.g., "ls -la", "cat file.txt", "find . -name *.pl")',
                    },
                    working_dir => {
                        type        => 'string',
                        description => 'Directory to run command in (optional, defaults to current directory)',
                    },
                },
                required => ['command'],
            },
            sub {
                my ($params, $loop) = @_;
                return _execute_command($session, $params, $loop);
            },
        ),

        # get_working_directory tool - safe, no confirmation needed
        tool(
            'get_working_directory',
            'Get the current working directory. This is safe and does not require user confirmation.',
            {
                type       => 'object',
                properties => {},
            },
            sub {
                my ($params, $loop) = @_;
                my $future = $loop->new_future;
                $future->done(_mcp_result(getcwd()));
                return $future;
            },
        ),
    ];
}

sub _execute_command {
    my ($session, $params, $loop) = @_;

    my $command = $params->{command};
    my $dir = $params->{working_dir} // $session->working_dir;

    # Record in history
    push @{$session->_history}, {
        time    => _timestamp(),
        command => $command,
        status  => 'running',
    };

    my $future = $loop->new_future;
    my $stdout = '';
    my $stderr = '';

    my $process = IO::Async::Process->new(
        command => [ '/bin/sh', '-c', $command ],
        ($dir && -d $dir ? (setup => [ chdir => $dir ]) : ()),
        stdout => {
            into => \$stdout,
        },
        stderr => {
            into => \$stderr,
        },
        on_finish => sub {
            my ($self, $exitcode) = @_;
            my $exit_status = $exitcode >> 8;

            if ($exit_status != 0) {
                $session->_history->[-1]{status} = "exit $exit_status";
                my $output = $stderr || $stdout || "Command failed with exit code $exit_status";
                $future->done(_mcp_result($output));
            } else {
                $session->_history->[-1]{status} = 'success';
                $future->done(_mcp_result($stdout // ''));
            }
        },
        on_exception => sub {
            my ($self, $exception, $errno, $exitcode) = @_;
            $session->_history->[-1]{status} = 'error';
            $future->done(_mcp_result("Error: $exception", 1));
        },
    );

    $loop->add($process);

    return $future;
}

# Helper to format tool results in MCP format
sub _mcp_result {
    my ($text, $is_error) = @_;
    return {
        content  => [{ type => 'text', text => $text }],
        is_error => $is_error ? 1 : 0,
    };
}

sub _list_files {
    my ($session, $params, $loop) = @_;

    my $path = $params->{path} // '.';
    my $pattern = $params->{pattern} // '';
    my $long = $params->{long_format} // 1;
    my $hidden = $params->{hidden} // 0;

    # Build ls command
    my @opts;
    push @opts, '-l' if $long;
    push @opts, '-a' if $hidden;

    my $target = $pattern ? "$path/$pattern" : $path;
    my $cmd = "ls @opts $target 2>/dev/null || ls @opts $path";

    return _execute_command($session, { command => $cmd }, $loop);
}

sub _read_file {
    my ($session, $params, $loop) = @_;

    my $path = $params->{path};
    my $lines = $params->{lines};
    my $tail = $params->{tail};

    # Build read command
    my $cmd;
    if ($tail) {
        $cmd = "tail -n $tail " . _shell_quote($path);
    } elsif ($lines) {
        $cmd = "head -n $lines " . _shell_quote($path);
    } else {
        $cmd = "cat " . _shell_quote($path);
    }

    return _execute_command($session, { command => $cmd }, $loop);
}

sub _shell_quote {
    my ($str) = @_;
    $str =~ s/'/'\\''/g;
    return "'$str'";
}

sub _timestamp {
    my @t = localtime;
    return sprintf("%04d-%02d-%02d %02d:%02d:%02d",
        $t[5] + 1900, $t[4] + 1, $t[3], $t[2], $t[1], $t[0]);
}

=head1 AUTHOR

LNATION, C<< <email at lnation.org> >>

=head1 LICENSE AND COPYRIGHT

This software is Copyright (c) 2026 by LNATION.

This is free software, licensed under:

  The Artistic License 2.0 (GPL Compatible)

=cut

1;
