import { access, rm, unlink, writeFile } from "node:fs/promises";
import { join, resolve } from "node:path";
import { spawn } from "node:child_process";
import { copyBundledDemoConfig, createComposeFile, createComposeEnv, createNoClientComposeOverride, demoComposeFileName, demoConfigDirectory, demoEnvFileName, defaultComposeFileName, defaultComposeOverrideFileName, defaultEnvFileName, hasFiles, } from "./compose-template.js";
export const drivers = {
    compose: {
        target: "compose",
        async diagnostics(instance, context) {
            const messages = [];
            if (!(await resolveDockerExecutable())) {
                messages.push("Docker was not found in a supported install location.");
            }
            for (const composeFile of getComposeFiles(instance)) {
                try {
                    await access(resolve(context.cwd, composeFile));
                }
                catch {
                    messages.push(`Compose file not found: ${composeFile}`);
                }
            }
            return messages;
        },
        up(options) {
            return runCompose(["up", "-d", ...options.args], options);
        },
        down(options) {
            return runCompose(["down", ...options.args], options);
        },
        logs(options) {
            return runCompose(["logs", "-f", ...options.args], options);
        },
    },
    external: {
        target: "external",
        async diagnostics() {
            return [];
        },
    },
};
export async function ensureComposeProject(cwd, options) {
    const mode = options.mode ?? "standard";
    const composeFileName = mode === "demo" ? demoComposeFileName : defaultComposeFileName;
    const envFileName = mode === "demo" ? demoEnvFileName : defaultEnvFileName;
    const composePath = join(cwd, composeFileName);
    const overridePath = join(cwd, defaultComposeOverrideFileName);
    const envPath = join(cwd, envFileName);
    if (mode === "demo" && options.reset === true) {
        await removeDemoProjectAssets(cwd);
    }
    if (!(await exists(composePath)) || options.force === true) {
        await writeFile(composePath, await createComposeFile(), "utf8");
    }
    if (!(await exists(envPath)) || options.force === true) {
        await writeFile(envPath, createComposeEnv({
            mode,
            cliVersion: options.cliVersion,
            imageTagOverride: options.imageTagOverride,
        }), {
            encoding: "utf8",
            mode: 0o600,
        });
    }
    if (mode === "demo") {
        const demoConfigPath = join(cwd, demoConfigDirectory);
        const hasExistingConfig = await hasFiles(demoConfigPath);
        if (!hasExistingConfig || options.force === true || options.reset === true) {
            await copyBundledDemoConfig(demoConfigPath, true);
        }
    }
    if (options.noClient === true) {
        await writeFile(overridePath, createNoClientComposeOverride(), "utf8");
    }
    else {
        await removeIfExists(overridePath);
    }
    const composeFiles = [composeFileName];
    if (options.noClient === true) {
        composeFiles.push(defaultComposeOverrideFileName);
    }
    return {
        target: "compose",
        url: "http://localhost:3000",
        clientUrl: options.noClient === true ? undefined : "http://localhost:4200",
        composeFile: defaultComposeFileName,
        composeFiles,
        envFile: envFileName,
        projectName: "eudiplo-demo",
    };
}
export async function demoProjectExists(cwd) {
    return ((await exists(join(cwd, demoComposeFileName))) ||
        (await exists(join(cwd, demoEnvFileName))) ||
        (await exists(join(cwd, demoConfigDirectory))));
}
async function removeDemoProjectAssets(cwd) {
    await rm(join(cwd, demoComposeFileName), { force: true });
    await rm(join(cwd, demoEnvFileName), { force: true });
    await rm(join(cwd, defaultComposeOverrideFileName), { force: true });
    await rm(join(cwd, demoConfigDirectory), { recursive: true, force: true });
}
export function unsupportedCommand(command, target) {
    if (target === "external") {
        return `${command} is not available for externally managed deployments`;
    }
    return `${command} is not available for ${target} deployments`;
}
async function runCompose(args, { instance, context }) {
    const composeArgs = ["compose"];
    if (instance.envFile) {
        composeArgs.push("--env-file", resolve(context.cwd, instance.envFile));
    }
    for (const composeFile of getComposeFiles(instance)) {
        composeArgs.push("-f", resolve(context.cwd, composeFile));
    }
    if (instance.projectName) {
        composeArgs.push("--project-name", instance.projectName);
    }
    composeArgs.push(...args);
    const dockerExecutable = await resolveDockerExecutable();
    if (!dockerExecutable) {
        context.stderr.write("Docker was not found in a supported install location.\n");
        return 1;
    }
    return new Promise((resolveProcess) => {
        const child = spawn(dockerExecutable, composeArgs, {
            cwd: context.cwd,
            env: context.env,
            stdio: "inherit",
        });
        child.on("error", (error) => {
            context.stderr.write(`${error.message}\n`);
            resolveProcess(1);
        });
        child.on("close", (code) => resolveProcess(code ?? 1));
    });
}
async function resolveDockerExecutable() {
    const executablePaths = process.platform === "win32"
        ? [String.raw `C:\Program Files\Docker\Docker\resources\bin\docker.exe`]
        : ["/usr/local/bin/docker", "/opt/homebrew/bin/docker", "/usr/bin/docker"];
    for (const executablePath of executablePaths) {
        if (await exists(executablePath)) {
            return executablePath;
        }
    }
    return undefined;
}
async function exists(path) {
    try {
        await access(path);
        return true;
    }
    catch {
        return false;
    }
}
function getComposeFiles(instance) {
    return instance.composeFiles ?? (instance.composeFile ? [instance.composeFile] : []);
}
async function removeIfExists(path) {
    try {
        await unlink(path);
    }
    catch (error) {
        if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
            throw error;
        }
    }
}
//# sourceMappingURL=drivers.js.map