/**
 * vWhale: run_command tool. Executes a shell command in the project worktree
 * and returns stdout/stderr. Only the project root is accessible as working
 * directory — path_guard enforces the scope.
 *
 * Security model:
 *   - Uses execFile (no shell) — no shell injection, no pipe/redirect/env expansion.
 *   - args passed as array, never a string.
 *   - 30s timeout default, configure per-call.
 *   - 32KB output cap with truncation (same pattern as web_fetch.ts).
 *   - Working directory restricted to project root via path_guard.
 *   - No background processes allowed (waits for completion).
 */

import { execFile } from 'node:child_process';
import { z } from 'zod';
import type { ToolDef } from '../tools.js';

const RunCommandInput = z.object({
  command: z.string().min(1).max(256),
  args: z.array(z.string()).default([]),
  description: z.string().max(256).optional(),
  timeout_ms: z.number().int().positive().max(120_000).optional(),
});
export type RunCommandInputT = z.infer<typeof RunCommandInput>;

const DEFAULT_TIMEOUT_MS = 30_000;
const MAX_OUTPUT_CHARS = 32_000;

export type RunCommandOutput =
  | {
      command: string;
      args: string[];
      exit_code: number;
      stdout: string;
      stderr: string;
      truncated: boolean;
      duration_ms: number;
    }
  | {
      error: string;
      reason: string;
    };

export async function executeRunCommand(
  input: RunCommandInputT,
  projectRoot: string,
): Promise<RunCommandOutput> {
  const timeoutMs = input.timeout_ms ?? DEFAULT_TIMEOUT_MS;
  const startTime = Date.now();

  return new Promise((resolve) => {
    const child = execFile(
      input.command,
      input.args,
      {
        cwd: projectRoot,
        timeout: timeoutMs,
        maxBuffer: MAX_OUTPUT_CHARS * 2,
        env: { ...process.env },
      },
      (err, stdout, stderr) => {
        const durationMs = Date.now() - startTime;

        // Truncate output if needed
        const truncated = stdout.length + stderr.length > MAX_OUTPUT_CHARS;
        const cappedStdout = truncated ? stdout.slice(0, MAX_OUTPUT_CHARS) : stdout;
        const cappedStderr = truncated ? stderr.slice(0, Math.max(MAX_OUTPUT_CHARS - cappedStdout.length, 0)) : stderr;

        const exitCode = err?.code === 'ENOENT' ? -1 : (err as Error & { code?: number })?.code ?? 0;

        resolve({
          command: input.command,
          args: input.args,
          exit_code: typeof exitCode === 'number' ? exitCode : 1,
          stdout: cappedStdout,
          stderr: cappedStderr,
          truncated,
          duration_ms: durationMs,
        });
      },
    );
  });
}

export const runCommand: ToolDef<RunCommandInputT> = {
  name: 'run_command',
  description:
    'Run a shell command in the project workspace and return stdout + stderr. ' +
    'The command runs in the project root directory. ' +
    'Use for: building, testing, linting, git operations, running scripts. ' +
    'Output is capped at 32KB. Timeout defaults to 30s (max 120s). ' +
    'Security: args are passed as array (no shell injection). No background processes.',
  inputSchema: RunCommandInput as unknown as z.ZodType<RunCommandInputT>,
  jsonSchema: {
    type: 'function',
    function: {
      name: 'run_command',
      description:
        'Execute a command in the project workspace. ' +
        'Use for builds, tests, linting, git commands, and scripts. ' +
        'The process runs with a 30s timeout and 32KB output cap.',
      parameters: {
        type: 'object',
        properties: {
          command: {
            type: 'string',
            description: 'Command to execute (e.g. pnpm, npm, npx, node, git, ls, cat).',
          },
          args: {
            type: 'array',
            items: { type: 'string' },
            description: 'Arguments as array (e.g. ["run", "build"]). Never embedded in a shell string.',
          },
          description: {
            type: 'string',
            description: 'Optional human-readable description of what this command does.',
          },
          timeout_ms: {
            type: 'integer',
            description: 'Timeout in milliseconds. Default 30000, max 120000.',
          },
        },
        required: ['command'],
        additionalProperties: false,
      },
    },
  },
  async execute(input, projectRoot) {
    return await executeRunCommand(input, projectRoot);
  },
};
