/**
 * host-side-proxy.ts — Phase A A3 of olam-k3d-on-mac-substrate-decision.
 *
 * Manages the host-side `tecnativa/docker-socket-proxy` container that
 * fronts the operator's docker daemon on macOS+colima+virtiofs. Wraps
 * `docker compose -f <yaml> {up,down,ps}` calls with idempotent semantics.
 *
 * Background — round-4 wave-2 R4-W2-F: an in-cluster docker-socket-proxy
 * Pod would itself need a hostPath bind mount of /var/run/docker.sock and
 * hit virtiofs ENOTSUP. The proxy MUST live on the operator's docker
 * daemon (sibling to k3d), not inside the k3d cluster. This helper is
 * what `olam upgrade` Step 0.7 calls to ensure that sibling container
 * is running before host-cp pods reference the in-cluster ExternalName
 * Service (packages/host-cp/k8s/manifests/docker-socket-proxy/60-service.yaml).
 *
 * The compose yaml managed here:
 *   packages/host-cp/k8s/host-side/docker-socket-proxy.compose.yaml
 *
 * Resolution: shipped in the npm tarball via packages/cli/package.json's
 * `files` glob; resolved at runtime relative to the CLI's install location
 * via resolveHostSideProxyComposePath (read by callers; this module
 * accepts the path as a dep for testability).
 *
 * Idempotency: `docker compose up -d` is idempotent at the docker level
 * (running container → no-op success). `docker compose down` removes
 * the container even if absent. `ps` returns running/stopped/absent.
 *
 * D20 stdin-safe: this module never accepts secret values as argv; the
 * proxy container has no secrets. Plain `docker compose` invocation.
 */
import { spawnSync } from 'node:child_process';
/**
 * Probe `docker compose version`. Returns ok=false with remediation when
 * the compose plugin is absent. Required preflight before up/down/ps.
 */
function probeDockerCompose(deps) {
    const spawn = deps.spawnSyncImpl ?? spawnSync;
    const env = deps.dockerContext !== undefined
        ? { ...process.env, DOCKER_CONTEXT: deps.dockerContext }
        : process.env;
    const r = spawn('docker', ['compose', 'version'], {
        env,
        encoding: 'utf8',
        timeout: 5000,
    });
    if (r.error !== undefined || r.status !== 0) {
        return {
            ok: false,
            reason: 'docker compose plugin not found. Install via `brew install docker-compose` ' +
                'or the Docker Desktop installer. The legacy `docker-compose` binary is NOT ' +
                'a substitute — olam requires `docker compose` (v2 plugin) for the host-side ' +
                'docker-socket-proxy lifecycle on macOS.',
        };
    }
    return { ok: true };
}
/**
 * Start the host-side proxy container. Idempotent: running container
 * → no-op success.
 *
 * @param composePath Absolute path to docker-socket-proxy.compose.yaml.
 */
export function startHostSideProxy(composePath, deps = {}) {
    const probe = probeDockerCompose(deps);
    if (!probe.ok) {
        return probe;
    }
    const spawn = deps.spawnSyncImpl ?? spawnSync;
    const env = deps.dockerContext !== undefined
        ? { ...process.env, DOCKER_CONTEXT: deps.dockerContext }
        : process.env;
    const r = spawn('docker', ['compose', '-f', composePath, 'up', '-d'], {
        env,
        encoding: 'utf8',
        timeout: 60_000,
    });
    if (r.error !== undefined) {
        return { ok: false, reason: `docker compose up failed: ${r.error.message}` };
    }
    if (r.status !== 0) {
        const stderr = (r.stderr ?? '').toString().trim();
        return {
            ok: false,
            reason: `docker compose -f ${composePath} up -d exited ${r.status ?? 'null'}: ${stderr || '(no stderr)'}. ` +
                `Check: \`docker context ls\` shows colima active; \`docker info\` succeeds; the compose ` +
                `file exists at the printed path.`,
        };
    }
    return { ok: true };
}
/**
 * Stop the host-side proxy container. Idempotent: absent container
 * → no-op success.
 */
export function stopHostSideProxy(composePath, deps = {}) {
    const probe = probeDockerCompose(deps);
    if (!probe.ok) {
        return probe;
    }
    const spawn = deps.spawnSyncImpl ?? spawnSync;
    const env = deps.dockerContext !== undefined
        ? { ...process.env, DOCKER_CONTEXT: deps.dockerContext }
        : process.env;
    const r = spawn('docker', ['compose', '-f', composePath, 'down'], {
        env,
        encoding: 'utf8',
        timeout: 30_000,
    });
    if (r.error !== undefined) {
        return { ok: false, reason: `docker compose down failed: ${r.error.message}` };
    }
    if (r.status !== 0) {
        const stderr = (r.stderr ?? '').toString().trim();
        return {
            ok: false,
            reason: `docker compose down exited ${r.status ?? 'null'}: ${stderr || '(no stderr)'}.`,
        };
    }
    return { ok: true };
}
/**
 * Status of the host-side proxy container. Returns 'running' / 'stopped' /
 * 'unknown' based on `docker compose ps --format json`. 'unknown' indicates
 * a probe failure (plugin absent, daemon unreachable, malformed output).
 */
export function statusHostSideProxy(composePath, deps = {}) {
    const probe = probeDockerCompose(deps);
    if (!probe.ok) {
        return { ok: false, status: 'unknown', reason: probe.reason };
    }
    const spawn = deps.spawnSyncImpl ?? spawnSync;
    const env = deps.dockerContext !== undefined
        ? { ...process.env, DOCKER_CONTEXT: deps.dockerContext }
        : process.env;
    const r = spawn('docker', ['compose', '-f', composePath, 'ps', '--format', 'json'], {
        env,
        encoding: 'utf8',
        timeout: 10_000,
    });
    if (r.error !== undefined || r.status !== 0) {
        return {
            ok: false,
            status: 'unknown',
            reason: `docker compose ps failed: ${(r.stderr ?? r.error?.message ?? '(no detail)').toString().trim()}`,
        };
    }
    const stdout = (r.stdout ?? '').toString().trim();
    if (stdout === '' || stdout === '[]') {
        return { ok: true, status: 'stopped' };
    }
    // Compose v2 `--format json` emits either a JSON array OR newline-delimited
    // JSON objects depending on plugin version. Handle both.
    try {
        const parsed = stdout.startsWith('[')
            ? JSON.parse(stdout)
            : stdout.split('\n').filter((line) => line.trim() !== '').map((line) => JSON.parse(line));
        if (!Array.isArray(parsed) || parsed.length === 0) {
            return { ok: true, status: 'stopped' };
        }
        const allRunning = parsed.every((entry) => {
            if (typeof entry !== 'object' || entry === null) {
                return false;
            }
            const state = entry.State;
            return typeof state === 'string' && state.toLowerCase() === 'running';
        });
        return { ok: true, status: allRunning ? 'running' : 'stopped' };
    }
    catch {
        return {
            ok: false,
            status: 'unknown',
            reason: 'docker compose ps emitted unparseable JSON; check `docker compose version`.',
        };
    }
}
//# sourceMappingURL=host-side-proxy.js.map