/**
 * exec.mjs — execFile-only execution layer for local_command transports.
 *
 * ID-03 security invariant: drivers with `transport: { local_command }`
 * MUST be invoked through execFile (NO shell, NO pipes, NO redirects).
 *
 * This module exposes ONE function — execLocalCommand — which:
 *   1. Asserts the transport is { local_command: { binary, args[] } }
 *      (the manifest validator already structurally enforces this; this
 *      is defense-in-depth at runtime).
 *   2. Rejects any args entry that is not a plain string.
 *   3. Invokes node:child_process.execFile (NOT child_process.exec, NOT
 *      spawn with shell:true).
 *
 * Grep guard (TD-05): the test verify-proof-local-command-security.test.ts
 * statically asserts that exec.mjs imports execFile from
 * 'node:child_process' and never references child_process.exec / spawn
 * with shell:true.
 */

import { execFile } from 'node:child_process'
import { promisify } from 'node:util'

const execFileAsync = promisify(execFile)

/**
 * Type-check that a transport is the structured local_command variant.
 * @param {unknown} transport
 * @returns {transport is {local_command: {binary: string, args: string[]}}}
 */
function isStructuredLocalCommand(transport) {
	if (transport === null || typeof transport !== 'object' || Array.isArray(transport)) return false
	const obj = /** @type {Record<string, unknown>} */ (transport)
	if (obj.local_command === null || typeof obj.local_command !== 'object') return false
	const lc = /** @type {Record<string, unknown>} */ (obj.local_command)
	if (typeof lc.binary !== 'string') return false
	if (!Array.isArray(lc.args)) return false
	for (const a of lc.args) {
		if (typeof a !== 'string') return false
	}
	return true
}

/**
 * Execute a manifest's local_command transport via execFile (no shell).
 * Throws if the transport is not the structured variant.
 *
 * @param {{transport: unknown}} manifest
 * @param {string[]} [extraArgs] — appended to manifest.transport.local_command.args.
 * @param {{cwd?: string, env?: Record<string,string>, timeout?: number}} [options]
 * @returns {Promise<{stdout: string, stderr: string}>}
 */
export async function execLocalCommand(manifest, extraArgs = [], options = {}) {
	const transport = manifest.transport
	if (!isStructuredLocalCommand(transport)) {
		throw new Error(
			`execLocalCommand: transport is not a structured local_command — refusing to invoke. Got: ${JSON.stringify(transport)}`,
		)
	}
	const { binary, args } = transport.local_command
	for (const arg of extraArgs) {
		if (typeof arg !== 'string') {
			throw new Error(`execLocalCommand: extraArgs must be strings, got ${typeof arg}`)
		}
	}
	return execFileAsync(binary, [...args, ...extraArgs], {
		cwd: options.cwd,
		env: options.env,
		timeout: options.timeout,
		// Explicit — never pass shell:true. execFile defaults to no shell, but
		// we surface the invariant in code for grep-guard purposes.
		shell: false,
	})
}
