import Dockerode from "dockerode";
import { logger } from "./logger.js";
function isDockerNoSuchImageError(err) {
    const e = err;
    return e?.statusCode === 404;
}
function isDockerDaemonUnavailable(err) {
    const e = err;
    if (e?.statusCode !== 500)
        return false;
    // Dockerode parses an empty response body as { type: "Buffer", data: [] }
    const json = e?.json;
    if (json && typeof json === "object" && json.type === "Buffer") {
        const data = json.data;
        if (Array.isArray(data) && data.length === 0)
            return true;
    }
    // 500 with API version mismatch message — Docker Desktop not healthy or wrong API version
    const msg = e?.message ?? "";
    if (msg.includes("check if the server supports the requested API version")) {
        return true;
    }
    return false;
}
function isManifestMismatchError(err) {
    const msg = err?.message ?? "";
    return msg.includes("no matching manifest");
}
/**
 * Returns the Docker platform string for the current host architecture.
 * Mirrors the backend's getDockerPlatform() in cmd/libs/init.go.
 */
function getDockerPlatform() {
    return process.arch === "arm64" ? "linux/arm64" : "linux/amd64";
}
async function doPull(docker, imageName, platform) {
    await new Promise((resolve, reject) => {
        docker.pull(imageName, { platform }, (err, stream) => {
            if (err)
                return reject(err);
            if (!stream)
                return reject(new Error("No stream received from docker pull"));
            docker.modem.followProgress(stream, (err) => {
                if (err)
                    return reject(err);
                logger.info("Docker image pulled successfully", {
                    image: imageName,
                    platform,
                });
                resolve();
            });
        });
    });
}
/**
 * Pull a Docker image by name.
 *
 * When `dockerPlatform` is provided, pulls with that exact platform — no
 * fallback logic runs.  Use this for images whose platform is known ahead
 * of time (e.g. executor is amd64-only).
 *
 * When `dockerPlatform` is omitted, uses the host architecture and falls
 * back to linux/amd64 if the arm64 pull fails or the image doesn't land
 * locally (Docker Desktop quirk on Apple Silicon).
 */
export async function pullDockerImage(imageName, dockerPlatform) {
    const client = new Dockerode();
    if (dockerPlatform) {
        await doPull(client, imageName, dockerPlatform);
        return;
    }
    const platform = getDockerPlatform();
    try {
        await doPull(client, imageName, platform);
    }
    catch (err) {
        if (platform === "linux/arm64" && isManifestMismatchError(err)) {
            logger.info("arm64 pull failed, retrying with linux/amd64 (emulation on Apple Silicon)", { image: imageName });
            await doPull(client, imageName, "linux/amd64");
            return;
        }
        throw err;
    }
    // On Docker Desktop/Mac, pulling an amd64-only image with linux/arm64 can
    // complete the stream without error yet not store the image locally.
    // Verify it actually landed, and fall back to amd64 if not.
    if (platform === "linux/arm64" &&
        !(await dockerImageExistsLocally(imageName))) {
        logger.info("arm64 pull completed but image not found locally, retrying with linux/amd64", { image: imageName });
        await doPull(client, imageName, "linux/amd64");
    }
}
/**
 * Check if a Docker image exists locally for the given reference (repo:tag or digest).
 * Uses image inspect (daemon name resolution), not listImages tag scanning.
 * Returns true if found, false if the image is not present (404).
 * Throws for any other error (e.g. Docker daemon unreachable) so callers can skip pulling.
 */
export async function dockerImageExistsLocally(imageName) {
    try {
        const client = new Dockerode();
        await client.getImage(imageName).inspect();
        return true;
    }
    catch (err) {
        if (isDockerNoSuchImageError(err))
            return false;
        if (isDockerDaemonUnavailable(err)) {
            logger.warning("Docker daemon is unavailable (returned 500 with empty body). Ensure Docker Desktop is running and healthy.", { image: imageName, error: err });
            throw err;
        }
        logger.warning("Could not check Docker image locally", {
            image: imageName,
            error: err,
        });
        throw err;
    }
}
