/**
 * Shell Tool - Command execution
 *
 * Implements T-207: shell.run
 * Emits command.started/completed events; streaming stdout/stderr.
 *
 * @module packages/runtime/src/tools/shell
 */

import { spawn } from 'child_process'
import type { ToolDefinition, ToolCall, ToolResultEnvelope, ISOTimeString } from '@aircoding/contracts'

export const shell_run: ToolDefinition = {
  name: 'shell.run',
  category: 'execute',
  description: 'Run a shell command',
  input_schema: {
    type: 'object',
    properties: {
      command: { type: 'string', description: 'Command to execute' },
      workdir: { type: 'string', description: 'Working directory' },
      timeout: { type: 'number', default: 300000, description: 'Timeout in milliseconds' },
      env: { type: 'object', description: 'Environment variables to add' }
    },
    required: ['command']
  },
  permissions: { read: false, write: false, network: true },
  streaming: true
}

export function createShellExecutor(project_root: string) {
  return {
    'shell.run': async function* (call: ToolCall, context: ToolExecutionContext): AsyncGenerator<ToolResultEnvelope> {
      const { command, workdir, timeout = 300000, env = {} } = call.arguments as {
        command: string
        workdir?: string
        timeout?: number
        env?: Record<string, string>
      }

      const cwd = workdir || project_root
      const timestamp = new Date().toISOString() as ISOTimeString

      // Emit command.started event
      yield {
        call_id: call.id,
        tool_name: 'shell.run',
        type: 'text',
        content: { event: 'command.started', command, cwd },
        metadata: { timestamp, streaming: true }
      }

      // Execute command
      const proc = spawn(command, [], {
        cwd,
        shell: true,
        env: { ...process.env, ...env }
      })

      let stdout = ''
      let stderr = ''
      let final_code = 0

      // Stream stdout
      proc.stdout.on('data', (data) => {
        const text = data.toString()
        stdout += text
        // Emit streaming stdout
        // Note: In actual implementation, this would go through EventBus
      })

      // Stream stderr
      proc.stderr.on('data', (data) => {
        const text = data.toString()
        stderr += text
      })

      // Wait for completion or timeout
      let timed_out = false
      const timeoutPromise = new Promise<number>((resolve) => {
        setTimeout(() => {
          timed_out = true
          proc.kill('SIGKILL')
          resolve(124) // standard timeout exit code
        }, timeout)
      })

      const exitCode = await Promise.race([
        new Promise<number>((resolve) => proc.on('exit', (code) => resolve(code || 0))),
        timeoutPromise
      ])

      final_code = exitCode
      if (timed_out) {
        stderr += `\n[Command timed out after ${timeout}ms]`
      }

      // Emit command.completed event
      yield {
        call_id: call.id,
        tool_name: 'shell.run',
        type: final_code === 0 ? 'text' : 'error',
        content: {
          event: 'command.completed',
          exit_code: final_code,
          stdout: stdout.slice(-50000), // Last 50KB
          stderr: stderr.slice(-10000), // Last 10KB
          timed_out
        },
        metadata: { timestamp: new Date().toISOString() as ISOTimeString, streaming: false }
      }
    }
  }
}

interface ToolExecutionContext {
  session_id: string
  project_id: string
  project_root: string
  agent_id: string
  agent_type: string
}