import { glob } from 'glob';
import fs from 'node:fs';
import path from 'node:path';
import { Writable } from 'node:stream';
import tar from 'tar-fs';
import constants from '../constants.js';
import OutputManager from '../outputManager.js';
const { ANSI_CODES } = constants;
export default class Container {
    command;
    containerRef;
    docker;
    egress;
    env;
    imageName;
    mounts;
    pwd;
    constructor({ command, docker, egress, env, imageName, mounts, pwd }) {
        this.docker = docker;
        this.imageName = imageName;
        this.command = command;
        this.env = env;
        this.pwd = pwd;
        this.mounts = mounts;
        this.egress = egress;
    }
    async create() {
        if (this.containerRef) {
            throw new Error("Container already exists.");
        }
        this.containerRef = await this.docker.createContainer({
            Cmd: this.command,
            Env: this.getDockerEnv(),
            HostConfig: {
                Binds: Object.entries(this.mounts || {}).map(([path, volume]) => `${volume.getName()}:${path}`)
            },
            Image: this.imageName,
            NetworkDisabled: this.egress === false,
            platform: constants.RUNTIME_ARCH,
            Tty: false,
            WorkingDir: this.pwd
        });
    }
    async destroy() {
        if (!this.containerRef) {
            throw new Error("There is no container to destroy.");
        }
        await this.containerRef.remove({ force: true });
    }
    async getFiles(localBasePath, filePathMap) {
        if (!this.containerRef) {
            throw new Error("There is no container from which to get files.");
        }
        for (const [localRelativePath, remoteFullPath] of Object.entries(filePathMap)) {
            const localFullPath = path.join(localBasePath, localRelativePath);
            OutputManager.detail(`Transferring ${remoteFullPath} => ${localFullPath}`);
            const destDir = path.dirname(localFullPath);
            if (!fs.existsSync(destDir)) {
                fs.mkdirSync(destDir, { recursive: true });
                OutputManager.detail(`Created directory: ${destDir}`);
            }
            // eslint-disable-next-line no-await-in-loop
            const tarStream = await this.containerRef.getArchive({ path: remoteFullPath });
            const extractStream = tar.extract(destDir);
            tarStream.pipe(extractStream);
            // eslint-disable-next-line no-await-in-loop
            await new Promise((resolve, reject) => {
                extractStream.on('finish', resolve);
                extractStream.on('error', reject);
            });
        }
    }
    async putFiles(srcRoot, srcPaths, destPath) {
        if (!this.containerRef) {
            throw new Error("There is no container to which to send files.");
        }
        const matchedFiles = new Set();
        for (const pattern of srcPaths) {
            const files = glob.sync(pattern, { cwd: srcRoot });
            for (const file of files) {
                OutputManager.detail(`Adding file to package: ${file}`);
                matchedFiles.add(file);
            }
        }
        const files = [...matchedFiles];
        const archive = tar.pack(srcRoot, { entries: files });
        return this.containerRef.putArchive(archive, { path: destPath });
    }
    async run() {
        if (!this.containerRef) {
            throw new Error("There is no container to run.");
        }
        const stream = await this.containerRef.attach({
            stderr: true,
            stdout: true,
            stream: true
        });
        const stdoutStream = this.createWritableStream(ANSI_CODES.GREEN, process.stdout);
        const stderrStream = this.createWritableStream(ANSI_CODES.RED, process.stderr);
        this.docker.modem.demuxStream(stream, stdoutStream, stderrStream);
        await this.containerRef.start();
        const result = await this.containerRef.wait();
        return result.StatusCode;
    }
    createWritableStream(color, output) {
        return new Writable({
            write(chunk, encoding, callback) {
                output.write(color + chunk.toString() + ANSI_CODES.RESET);
                callback();
            }
        });
    }
    getDockerEnv() {
        // Convert env from Record<string, string> to string[] format for Docker
        return this.env ? Object.entries(this.env).map(([key, value]) => `${key}=${value}`) : [];
    }
}
