/**
 * Bash tool — execute shell commands in the workspace.
 */

import { spawn } from 'child_process';
import { resolve, normalize } from 'path';

const MAX_OUTPUT = 30000;
const DEFAULT_TIMEOUT = 120_000;
const MAX_TIMEOUT = 600_000;

// Commands blocked for security reasons
const BLOCKED_PATTERNS = [
  /\brm\s+(-rf|-fr)\s+[/\\]\s*$/,   // rm -rf /
  /\bformat\b/i,
  /\bmkfs\b/,
  /\bdd\s+if=/,
  /\b:(){ :|:& };:/,                  // fork bomb
];

export const definition = {
  name: 'Bash',
  description: 'Execute a shell command. Use for git, npm, build commands, and process management. Do NOT use for file reading (use Read), searching (use Grep/Glob), or file editing (use Edit).',
  input_schema: {
    type: 'object',
    properties: {
      command: { type: 'string', description: 'The command to execute' },
      timeout: { type: 'number', description: 'Optional timeout in milliseconds (max 600000)' },
      description: { type: 'string', description: 'Clear, concise description of what this command does' }
    },
    required: ['command']
  }
};

function isBlocked(command) {
  for (const pattern of BLOCKED_PATTERNS) {
    if (pattern.test(command)) return true;
  }
  return false;
}

export async function execute(input, context) {
  const { command, timeout: userTimeout, description } = input;

  if (!command) return { content: 'Error: command is required', is_error: true };

  if (isBlocked(command)) {
    return { content: `Error: Command blocked for security reasons: ${command.slice(0, 100)}`, is_error: true };
  }

  const timeout = Math.min(userTimeout || DEFAULT_TIMEOUT, MAX_TIMEOUT);
  const cwd = context.workspaceRoot || process.cwd();

  return new Promise((resolve_) => {
    let stdout = '';
    let stderr = '';
    let killed = false;

    const isWindows = process.platform === 'win32';
    const shell = isWindows ? 'cmd.exe' : '/bin/bash';
    const shellArgs = isWindows ? ['/d', '/s', '/c', command] : ['-c', command];

    const proc = spawn(shell, shellArgs, {
      cwd,
      env: { ...process.env },
      stdio: ['pipe', 'pipe', 'pipe'],
      shell: false
    });

    const timer = setTimeout(() => {
      killed = true;
      proc.kill('SIGTERM');
      setTimeout(() => { try { proc.kill('SIGKILL'); } catch {} }, 5000);
    }, timeout);

    proc.stdout.on('data', (chunk) => {
      stdout += chunk.toString();
      if (stdout.length > MAX_OUTPUT * 2) {
        // Truncate in-flight to prevent memory issues
        stdout = stdout.slice(0, MAX_OUTPUT) + '\n...[truncated]';
      }
    });

    proc.stderr.on('data', (chunk) => {
      stderr += chunk.toString();
      if (stderr.length > MAX_OUTPUT) {
        stderr = stderr.slice(0, MAX_OUTPUT) + '\n...[truncated]';
      }
    });

    proc.stdin.end();

    proc.on('close', (code) => {
      clearTimeout(timer);

      let output = '';
      if (stdout.trim()) output += stdout;
      if (stderr.trim()) output += (output ? '\n' : '') + stderr;

      // Truncate final output
      if (output.length > MAX_OUTPUT) {
        output = output.slice(0, MAX_OUTPUT) + '\n...[output truncated at 30000 chars]';
      }

      if (killed) {
        resolve_({ content: `Command timed out after ${timeout}ms.\n${output}`.trim(), is_error: true });
      } else if (code !== 0) {
        resolve_({ content: `Exit code ${code}\n${output}`.trim(), is_error: true });
      } else {
        resolve_({ content: output || '(no output)', is_error: false });
      }
    });

    proc.on('error', (err) => {
      clearTimeout(timer);
      resolve_({ content: `Spawn error: ${err.message}`, is_error: true });
    });
  });
}
