import Foundation
import VMAgentCore

extension ConnectionHandler {
    /// Handle a streaming **`exec`** request (docs/MACOS_VM_NATIVE_AGENT.md §4.4): spawn the command and
    /// pump its stdio over this connection as base64 NDJSON frames until it exits, forwarding any
    /// host→guest `stdin`/`signal`/`eof` frames to the child. Owns ``fd`` for the whole run; the caller
    /// closes the connection afterwards (exec connections are single-use).
    ///
    /// The command runs under **nash** (`/usr/local/bin/nash -lc`) — the ONLY shell the exec surface
    /// offers (docs/NASH.md §7.5, locked): a guest without nash replies with an actionable
    /// `spawnError` instead of degrading to another shell (provisioning hard-requires nash, so that
    /// only happens on a stale base). The lone exception is the in-guest operator break-glass
    /// `disable=1` in the root-owned `NashPolicy` file (§4.1, §4.3 — not an env var, which the
    /// agent could set for itself). The toolchain PATH resolves via `/etc/profile.d`, which
    /// provisioning mirrors from `/etc/zshenv` for nash's `-l` sourcing — so
    /// `xcodebuild`/`brew`/`node` resolve as before. The host has already composed any working
    /// directory + env exports into the script.
    ///
    /// All streaming/spawn/signal mechanics live in ``ExecSession`` (VMAgentCore) so the tests can
    /// drive a real session over a socketpair. The session spawns the shell as its own process-group
    /// leader and never lets surviving descendants hold the exit frame hostage — the two properties
    /// that keep `mac_vm_exec` from wedging (NASH.md §7.5).
    func runExec(requestLine: Data) {
        guard
            let parsed = try? JSONSerialization.jsonObject(with: requestLine),
            let request = parsed as? [String: Any]
        else {
            // Match ExecSession's malformed-request reply without spinning up a session.
            var line = Data(
                #"{"\#(AgentWire.Exec.tagKey)":"\#(AgentWire.Exec.spawnError)","\#(AgentWire.Exec.messageKey)":"exec request did not parse"}"#
                    .utf8)
            line.append(0x0A)
            _ = writeAll(line)
            return
        }
        // Hand over this connection's read buffer: any host→guest frames pipelined right after the
        // request line are in there, and the session keeps draining the same framing.
        ExecSession(fd: fd, buffered: lines).run(request: request)
    }
}
