"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.DockerService = void 0;
const utils_1 = require("../utils");
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const config_1 = require("../config");
const crypto = __importStar(require("crypto"));
class DockerService {
    static IMAGE_NAME = "knowhow-worker";
    static CONTAINER_PREFIX = "knowhow-worker";
    async checkDockerAvailable() {
        try {
            await (0, utils_1.execAsync)("docker --version");
            await (0, utils_1.execAsync)("docker info");
            return true;
        }
        catch (error) {
            console.log(error);
            return false;
        }
    }
    getDockerfilePath() {
        const buildContext = process.cwd();
        return path.join(buildContext, ".knowhow", "Dockerfile.worker");
    }
    async ensureDockerfile() {
        const dockerfilePath = this.getDockerfilePath();
        const fs = require("fs");
        const knowhowDir = path.dirname(dockerfilePath);
        if (!fs.existsSync(knowhowDir)) {
            fs.mkdirSync(knowhowDir, { recursive: true });
        }
        if (!fs.existsSync(dockerfilePath)) {
            console.log("📝 Creating default worker.Dockerfile...");
            const dockerfile = this.generateDockerfile();
            fs.writeFileSync(dockerfilePath, dockerfile);
            console.log(`✓ Dockerfile created at ${dockerfilePath}`);
            console.log("  You can customize this file to modify the worker image\n");
        }
        return dockerfilePath;
    }
    async buildWorkerImage() {
        console.log("🔨 Building Docker image for knowhow worker...");
        const dockerfilePath = await this.ensureDockerfile();
        const buildContext = process.cwd();
        console.log("📦 Starting Docker build process...\n");
        return new Promise((resolve, reject) => {
            const { spawn } = require("child_process");
            const buildProcess = spawn("docker", [
                "build",
                "-t",
                DockerService.IMAGE_NAME,
                "-f",
                dockerfilePath,
                buildContext,
            ], {
                stdio: ["inherit", "pipe", "pipe"],
            });
            buildProcess.stdout.on("data", (data) => {
                const output = data.toString();
                const lines = output.split("\n").filter((line) => line.trim());
                lines.forEach((line) => {
                    if (line.includes("Step ")) {
                        console.log(`🔧 ${line}`);
                    }
                    else if (line.includes("---> ")) {
                        console.log(`   ${line}`);
                    }
                    else if (line.trim()) {
                        console.log(`   ${line}`);
                    }
                });
            });
            buildProcess.stderr.on("data", (data) => {
                const output = data.toString();
                const lines = output.split("\n").filter((line) => line.trim());
                lines.forEach((line) => {
                    console.log(`⚠️  ${line}`);
                });
            });
            buildProcess.on("close", (code) => {
                if (code === 0) {
                    console.log("\n🎉 Docker image built successfully!");
                    console.log(`✅ Image '${DockerService.IMAGE_NAME}' is ready to use\n`);
                    resolve();
                }
                else {
                    reject(new Error(`Docker build failed with exit code ${code}`));
                }
            });
            buildProcess.on("error", (error) => {
                reject(error);
            });
            buildProcess.on("spawn", () => {
                console.log("🚀 Docker build process started...\n");
            });
        });
    }
    async imageExists() {
        try {
            const { stdout } = await (0, utils_1.execAsync)(`docker images -q ${DockerService.IMAGE_NAME}`);
            return stdout.trim().length > 0;
        }
        catch (error) {
            return false;
        }
    }
    expandPath(pathStr) {
        if (pathStr.startsWith("~/") || pathStr === "~") {
            return pathStr.replace("~", os.homedir());
        }
        return pathStr;
    }
    parseVolumeMount(volumeSpec) {
        const parts = volumeSpec.split(":");
        if (parts.length < 2) {
            return {
                hostPath: "",
                containerPath: "",
                isValid: false,
                error: `Invalid volume format: "${volumeSpec}". Expected format: "host_path:container_path[:mode]"`,
            };
        }
        const hostPath = this.expandPath(parts[0]);
        const containerPath = parts[1];
        const mode = parts[2];
        const fs = require("fs");
        if (!fs.existsSync(hostPath)) {
            return {
                hostPath,
                containerPath,
                mode,
                isValid: false,
                error: `Host path does not exist: ${hostPath} (from "${volumeSpec}")`,
            };
        }
        return {
            hostPath,
            containerPath,
            mode,
            isValid: true,
        };
    }
    processVolumeMounts(volumes) {
        const valid = [];
        const errors = [];
        for (const volumeSpec of volumes) {
            const parsed = this.parseVolumeMount(volumeSpec);
            if (!parsed.isValid) {
                errors.push(parsed.error);
            }
            else {
                const mount = parsed.mode
                    ? `${parsed.hostPath}:${parsed.containerPath}:${parsed.mode}`
                    : `${parsed.hostPath}:${parsed.containerPath}`;
                valid.push(mount);
            }
        }
        return { valid, errors };
    }
    getWorkspaceHash(workspaceDir) {
        const hash = crypto.createHash("md5").update(workspaceDir).digest("hex");
        return hash.substring(0, 8);
    }
    async removeContainerByName(containerName) {
        try {
            const { stdout } = await (0, utils_1.execAsync)(`docker ps -a --filter "name=^/${containerName}$" --format "{{.ID}}"`);
            const containerId = stdout.trim();
            if (containerId) {
                console.log(`🗑️  Removing existing container: ${containerName}`);
                await (0, utils_1.execAsync)(`docker stop ${containerId}`);
                await (0, utils_1.execAsync)(`docker rm ${containerId}`);
            }
        }
        catch (error) {
        }
    }
    async runWorkerContainer(options) {
        const workspaceHash = this.getWorkspaceHash(options.workspaceDir);
        const containerName = `${DockerService.CONTAINER_PREFIX}-${workspaceHash}`;
        const homedir = os.homedir();
        const relativeWorkspace = options.workspaceDir.replace(homedir, "~");
        const knowhowDir = path.join(homedir, ".knowhow");
        const relativeKnowhowDir = "~/.knowhow";
        await this.removeContainerByName(containerName);
        let config = options.config;
        const defaultVolumes = [
            `${relativeWorkspace}:/workspace`,
            `${relativeKnowhowDir}:/root/.knowhow`,
        ];
        if (!config?.worker?.volumes || config.worker.volumes.length === 0) {
            console.log("📝 Initializing default volume mounts in config...");
            config = {
                ...config,
                worker: {
                    ...config?.worker,
                    volumes: defaultVolumes,
                },
            };
            await (0, config_1.updateConfig)(config);
            console.log("✓ Config updated with default volume mounts\n");
        }
        else {
            const configVolumes = config.worker.volumes;
            const missingDefaults = defaultVolumes.filter((dv) => !configVolumes.some((cv) => cv.split(":")[1] === dv.split(":")[1]));
            if (missingDefaults.length > 0) {
                console.log("📝 Adding missing default volumes to config...");
                config.worker.volumes = [...missingDefaults, ...configVolumes];
                await (0, config_1.updateConfig)(config);
                console.log("✓ Config updated with missing default volumes\n");
            }
        }
        const volumeMounts = [];
        console.log(`📁 Processing ${config.worker.volumes.length} volume mount(s)...`);
        const { valid, errors } = this.processVolumeMounts(config.worker.volumes);
        if (errors.length > 0) {
            console.warn("⚠️  Some volume mounts could not be processed:");
            errors.forEach((error) => console.warn(`   ${error}`));
        }
        if (valid.length > 0) {
            console.log(`✓ Mounting ${valid.length} volume(s):`);
            valid.forEach((mount) => {
                const parts = mount.split(":");
                const mode = parts[2] ? ` (${parts[2]})` : "";
                console.log(`   ${parts[0]} → ${parts[1]}${mode}`);
                volumeMounts.push(`-v "${mount}"`);
            });
        }
        else {
            console.error("❌ No valid volume mounts available!");
            throw new Error("Cannot start container without valid volume mounts");
        }
        const hostname = `${os.hostname()}.docker`;
        const workspaceRoot = process.env.WORKER_ROOT || relativeWorkspace;
        const envVars = [
            `-e KNOWHOW_JWT="${options.jwt}"`,
            `-e KNOWHOW_API_URL="${options.apiUrl}"`,
            `-e WORKSPACE_ROOT="${relativeWorkspace}"`,
            `-e WORKER_HOSTNAME="${hostname}"`,
            `-e WORKER_ROOT="${workspaceRoot}"`,
        ];
        if (options.share) {
            envVars.push(`-e WORKER_SHARED="true"`);
        }
        else if (options.unshare) {
            envVars.push(`-e WORKER_SHARED="false"`);
        }
        const envFileArgs = [];
        if (config?.worker?.envFile) {
            const envFilePath = this.expandPath(config.worker.envFile);
            const fs = require("fs");
            if (fs.existsSync(envFilePath)) {
                console.log(`📄 Using environment file: ${envFilePath}`);
                envFileArgs.push(`--env-file "${envFilePath}"`);
            }
            else {
                console.warn(`⚠️  Environment file not found: ${envFilePath}`);
                console.warn(`   Container will run without the environment file.`);
            }
        }
        const dockerCmd = [
            "docker run",
            "-d",
            `--name ${containerName}`,
            "--network host",
            ...volumeMounts,
            ...envVars,
            ...envFileArgs,
            `-w /workspace`,
            DockerService.IMAGE_NAME,
        ].join(" ");
        console.log("Starting Docker container...");
        try {
            const { stdout } = await (0, utils_1.execAsync)(dockerCmd);
            const containerId = stdout.trim();
            console.log(`✓ Container started: ${containerName} (${containerId.substring(0, 12)})`);
            console.log(`  Workspace: ${options.workspaceDir}`);
            console.log(`  Container ID: ${containerId.substring(0, 12)}`);
            return containerId;
        }
        catch (error) {
            console.error("Failed to start container:", error.message);
            throw error;
        }
    }
    async followContainerLogs(containerId) {
        console.log("\nFollowing container logs (Ctrl+C to stop)...\n");
        const { spawn } = require("child_process");
        const logsProcess = spawn("docker", ["logs", "-f", containerId], {
            stdio: "inherit",
        });
        return new Promise((resolve, reject) => {
            logsProcess.on("error", reject);
            process.on("SIGINT", () => {
                logsProcess.kill();
                resolve();
            });
        });
    }
    async stopContainer(containerId) {
        try {
            console.log(`\nStopping container ${containerId.substring(0, 12)}...`);
            await (0, utils_1.execAsync)(`docker stop ${containerId}`);
            await (0, utils_1.execAsync)(`docker rm ${containerId}`);
            console.log("✓ Container stopped and removed");
        }
        catch (error) {
            console.error("Failed to stop container:", error.message);
        }
    }
    async cleanupAllWorkerContainers() {
        try {
            const { stdout } = await (0, utils_1.execAsync)(`docker ps -a --filter "name=${DockerService.CONTAINER_PREFIX}" --format "{{.ID}}"`);
            const containerIds = stdout.trim().split("\n").filter(Boolean);
            if (containerIds.length === 0) {
                console.log("No worker containers to clean up");
                return;
            }
            console.log(`Cleaning up ${containerIds.length} worker container(s)...`);
            for (const containerId of containerIds) {
                await this.stopContainer(containerId);
            }
            console.log("✓ All worker containers cleaned up");
        }
        catch (error) {
            console.error("Failed to clean up containers:", error.message);
        }
    }
    generateDockerfile() {
        return `FROM node:20

# Install necessary system dependencies
RUN apt-get update && apt-get install -y \
curl \
ffmpeg \
&& rm -rf /var/lib/apt/lists/*

# Install necessary packages
RUN apt-get install git python3 make g++ bash curl

# Install knowhow CLI globally
RUN npm install -g @tyvm/knowhow

# Create workspace directory
WORKDIR /workspace

RUN knowhow init

# Set environment variables
ENV NODE_ENV=production
ENV KNOWHOW_DOCKER=true

# Set the default command to run the worker
CMD ["knowhow", "worker"]
`;
    }
}
exports.DockerService = DockerService;
//# sourceMappingURL=DockerService.js.map