package Acme::Claude::Shell::Hooks;

use 5.020;
use strict;
use warnings;

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

use Claude::Agent::CLI qw(menu status ask_yn prompt start_spinner stop_spinner);
use Claude::Agent::Hook::Matcher;
use Claude::Agent::Hook::Result;
use Term::ANSIColor qw(colored);

=head1 NAME

Acme::Claude::Shell::Hooks - Safety hooks for Acme::Claude::Shell

=head1 SYNOPSIS

    use Acme::Claude::Shell::Hooks qw(safety_hooks);

    my $hooks = safety_hooks($session);

=head1 DESCRIPTION

Provides PreToolUse and PostToolUse hooks for command safety:

=over 4

=item * Detects dangerous command patterns (rm -rf, sudo, dd, etc.)

=item * Shows colorful approval menu before execution

=item * Allows editing commands before running

=item * Supports dry-run mode to preview without executing

=item * Logs command history after execution

=back

=cut

# Dangerous command patterns with explanations
my @DANGEROUS_PATTERNS = (
    { pattern => qr/\brm\s+(-[rf]+|--recursive|--force)/i,
      reason  => 'Recursive or forced file deletion' },
    { pattern => qr/\bsudo\b/,
      reason  => 'Superuser command' },
    { pattern => qr/\bmkfs\b/,
      reason  => 'Filesystem formatting' },
    { pattern => qr/\bdd\b.*\bof=/,
      reason  => 'Direct disk write' },
    { pattern => qr/>\s*\/dev\//,
      reason  => 'Writing to device file' },
    { pattern => qr/\bchmod\s+(-R\s+)?777\b/,
      reason  => 'World-writable permissions' },
    { pattern => qr/\bchown\s+-R\b.*\//,
      reason  => 'Recursive ownership change' },
    { pattern => qr/\bkill\s+-9\b/,
      reason  => 'Forceful process termination' },
    { pattern => qr/\b(reboot|shutdown|halt|poweroff)\b/,
      reason  => 'System shutdown/reboot' },
    { pattern => qr/\bformat\b/,
      reason  => 'Disk formatting' },
    { pattern => qr/:\s*\(\s*\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;/,
      reason  => 'Fork bomb detected' },
    { pattern => qr/\bwget\b.*\|\s*(ba)?sh/i,
      reason  => 'Piping remote script to shell' },
    { pattern => qr/\bcurl\b.*\|\s*(ba)?sh/i,
      reason  => 'Piping remote script to shell' },
);

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

    # Tool name pattern - matches mcp__shell-tools__execute_command
    # Using regex to match the suffixed tool name
    my $execute_cmd_pattern = 'execute_command$';

    return {
        # PreToolUse: Confirm before executing commands
        PreToolUse => [
            Claude::Agent::Hook::Matcher->new(
                matcher => $execute_cmd_pattern,
                hooks   => [sub {
                    my ($input, $tool_use_id, $context) = @_;
                    return _confirm_command($session, $input->{tool_input});
                }],
            ),
        ],

        # PostToolUse: Stop spinner after command execution
        PostToolUse => [
            Claude::Agent::Hook::Matcher->new(
                matcher => $execute_cmd_pattern,
                hooks   => [sub {
                    my ($input, $tool_use_id, $context) = @_;
                    # Stop the execution spinner
                    if ($session->_spinner) {
                        stop_spinner($session->_spinner);
                        $session->_spinner(undef);
                    }
                    return Claude::Agent::Hook::Result->proceed();
                }],
            ),
        ],
    };
}

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

    my $command = $params->{command};
    my $colorful = $session->colorful;

    # Stop the spinner before reading STDIN (avoids Term::ProgressSpinner conflicts)
    if ($session->can('_spinner') && $session->_spinner) {
        stop_spinner($session->_spinner);
        $session->_spinner(undef);
    }

    # Check for dangerous patterns
    my $danger = _check_dangerous($command);

    print "\n";

    if ($danger && $session->safe_mode) {
        # Dangerous command detected - extra warning
        if ($colorful) {
            status('warning', "Potentially dangerous command detected!");
            print colored(['yellow'], "  Reason: $danger->{reason}\n");
        } else {
            print "WARNING: Potentially dangerous command!\n";
            print "  Reason: $danger->{reason}\n";
        }
        print "\n";
    }

    # Show the command
    if ($colorful) {
        status('info', "Command: $command");
    } else {
        print "Command: $command\n";
    }

    # Show action menu using Term::Choose for keyboard navigation
    my $choice = menu("Action", [
        { key => 'a', label => 'Approve and run' },
        { key => 'd', label => 'Dry-run (preview only)' },
        { key => 'e', label => 'Edit command' },
        { key => 'x', label => 'Cancel' },
    ]) // 'x';  # Default to cancel if menu returns undef

    # Handle choice
    if ($choice eq 'x') {
        if ($colorful) {
            status('warning', "Command cancelled");
        } else {
            print "Cancelled.\n";
        }
        return Claude::Agent::Hook::Result->deny(reason => 'User cancelled');
    }
    elsif ($choice eq 'd') {
        if ($colorful) {
            status('info', "[DRY-RUN] Would execute:");
            print colored(['cyan'], "  $command\n\n");
        } else {
            print "[DRY-RUN] Would execute: $command\n";
        }
        return Claude::Agent::Hook::Result->deny(reason => "Dry-run: $command");
    }
    elsif ($choice eq 'e') {
        my $new_cmd;
        if ($colorful) {
            $new_cmd = prompt("Edit command:", $command);
        } else {
            print "Edit command [$command]: ";
            $new_cmd = <STDIN>;
            chomp $new_cmd if defined $new_cmd;
            $new_cmd = $command unless length($new_cmd // '');
        }

        if ($colorful) {
            status('info', "Modified command:");
            print colored(['bold', 'white'], "  $new_cmd\n\n");
        } else {
            print "Modified: $new_cmd\n";
        }

        # Return allow with updated input
        print "\n";
        return Claude::Agent::Hook::Result->allow(
            updated_input => { command => $new_cmd },
        );
    }

    # For dangerous commands, require extra confirmation
    if ($danger && $session->safe_mode) {
        my $confirmed;
        if ($colorful) {
            $confirmed = ask_yn("Are you SURE you want to run this dangerous command?", 'n');
        } else {
            print "Are you SURE? (y/N): ";
            my $ans = <STDIN>;
            chomp $ans if defined $ans;
            $confirmed = ($ans // '') =~ /^y/i;
        }

        unless ($confirmed) {
            if ($colorful) {
                status('warning', "Command cancelled");
            } else {
                print "Cancelled.\n";
            }
            return Claude::Agent::Hook::Result->deny(reason => 'User cancelled dangerous command');
        }
    }

    # Start spinner for command execution
    if ($colorful) {
        $session->_spinner(start_spinner("Executing...", $session->loop));
    }

    return Claude::Agent::Hook::Result->allow();
}

sub _check_dangerous {
    my ($command) = @_;

    for my $check (@DANGEROUS_PATTERNS) {
        if ($command =~ $check->{pattern}) {
            return $check;
        }
    }

    return undef;
}

=head1 DANGEROUS PATTERNS

The following command patterns trigger extra warnings:

=over 4

=item * C<rm -rf> - Recursive/forced deletion

=item * C<sudo> - Superuser commands

=item * C<mkfs> - Filesystem formatting

=item * C<dd of=> - Direct disk writes

=item * C<chmod 777> - World-writable permissions

=item * C<kill -9> - Forceful process termination

=item * C<reboot/shutdown> - System restart

=item * C<wget/curl | sh> - Piping remote scripts to shell

=back

=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;
