// Nucleic in-container control bridge.
//
// Forwards loopback TCP -> the relayed host control socket. The agent's MCP client and the
// git/gh/command interceptor shims connect to 127.0.0.1:<port> (loopback, inside the VM's own
// network namespace — invisible to macOS, so no incoming-connection / local-network prompts); we
// pipe each connection to the unix socket the containerization framework relays in from the host
// approval server (`UnixSocketConfiguration(.into)`).
//
// Launched by the container's root init when (and only when) a control socket is relayed in
// (see ContainerEngine), so it can reach the root-owned relayed socket while the non-root agent
// only ever touches loopback TCP — no socket-permission juggling. It's a pure byte forwarder: the
// HTTP/JSON-RPC framing the host server speaks flows through untouched.
//
// Defaults MUST match the Swift side: ContainerSpec.controlBridgePort (9099) and
// ContainerSpec.controlSocketGuestPath (/run/nucleic/control.sock).
//
// On Windows (docs/WINDOWS_PORT.md §5) there is no vsock-relayed unix socket: the host
// approval server listens on the WSL-facing gateway interface instead, and the engine sets
// NUCLEIC_CONTROL_HOST/NUCLEIC_CONTROL_PORT (and NUCLEIC_PROXY_HOST/NUCLEIC_PROXY_PORT for
// the opt-in token proxy). When those are present the forwarder dials that TCP endpoint
// instead of the unix socket; everything else — ports, framing, supervision — is unchanged,
// so the agent-side contract is identical on both platforms.

const net = require("net");

const PORT = Number(process.env.NUCLEIC_BRIDGE_PORT || 9099);
const SOCK = process.env.NUCLEIC_BRIDGE_SOCKET || "/run/nucleic/control.sock";

// The upstream dial target: `{ path }` (relayed unix socket, macOS) or `{ host, port }`
// (gateway TCP, Windows/WSL) — both are valid `net.connect` option objects.
function target(hostEnv, portEnv, sock) {
  const host = process.env[hostEnv] || "";
  const port = Number(process.env[portEnv] || 0);
  return host && port > 0 ? { host, port } : { path: sock };
}

const describe = (t) => (t.path ? t.path : `${t.host}:${t.port}`);

// A pure byte forwarder: loopback TCP `port` -> the host control endpoint `upstreamTarget`.
// One upstream connection per inbound TCP connection, dialed lazily (on first connect), by
// which point the host side is listening. `fatal` distinguishes the always-required control
// forwarder (its listen failure exits so the whole container's control plane fails visibly)
// from the optional token-proxy forwarder (a listen failure is logged but must not take the
// control plane down with it).
function forwarder(port, upstreamTarget, label, fatal) {
  const server = net.createServer((client) => {
    const upstream = net.connect(upstreamTarget);
    const teardown = () => {
      client.destroy();
      upstream.destroy();
    };
    client.on("error", teardown);
    upstream.on("error", (err) => {
      // The host endpoint isn't reachable yet (host server not listening, or no relay) — drop this
      // connection; the client retries. Never crash the bridge over one failed connection.
      console.error(`nucleic-bridge[${label}]: upstream error`, err && err.message);
      teardown();
    });
    client.pipe(upstream);
    upstream.pipe(client);
  });

  server.on("error", (err) => {
    console.error(`nucleic-bridge[${label}]: listen error`, err && err.message);
    if (fatal) process.exit(1);
  });

  server.listen(port, "127.0.0.1", () => {
    console.error(`nucleic-bridge[${label}]: 127.0.0.1:${port} -> ${describe(upstreamTarget)}`);
  });
}

// The control forwarder is always required (MCP + git/gh/command interception).
forwarder(PORT, target("NUCLEIC_CONTROL_HOST", "NUCLEIC_CONTROL_PORT", SOCK), "control", true);

// The Claude token-proxy forwarder is opt-in: present only when the host relayed a proxy socket
// (or, on Windows, exposed a proxy TCP endpoint) and set these env vars (see ContainerEngine).
// Its failure must not sever the control plane.
const PROXY_PORT = Number(process.env.NUCLEIC_PROXY_BRIDGE_PORT || 0);
const PROXY_SOCK = process.env.NUCLEIC_PROXY_BRIDGE_SOCKET || "";
const proxyTarget = target("NUCLEIC_PROXY_HOST", "NUCLEIC_PROXY_PORT", PROXY_SOCK);
if (PROXY_PORT > 0 && (proxyTarget.path || proxyTarget.host)) {
  forwarder(PROXY_PORT, proxyTarget, "proxy", false);
}
