var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
    var useValue = arguments.length > 2;
    for (var i = 0; i < initializers.length; i++) {
        value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
    }
    return useValue ? value : void 0;
};
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
    function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
    var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
    var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
    var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
    var _, done = false;
    for (var i = decorators.length - 1; i >= 0; i--) {
        var context = {};
        for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
        for (var p in contextIn.access) context.access[p] = contextIn.access[p];
        context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
        var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
        if (kind === "accessor") {
            if (result === void 0) continue;
            if (result === null || typeof result !== "object") throw new TypeError("Object expected");
            if (_ = accept(result.get)) descriptor.get = _;
            if (_ = accept(result.set)) descriptor.set = _;
            if (_ = accept(result.init)) initializers.unshift(_);
        }
        else if (_ = accept(result)) {
            if (kind === "field") initializers.unshift(_);
            else descriptor[key] = _;
        }
    }
    if (target) Object.defineProperty(target, contextIn.name, descriptor);
    done = true;
};
import { spawn } from "child_process";
import { randomUUID } from "crypto";
import * as fs from "fs/promises";
import * as os from "os";
import * as path from "path";
import { reifyTemplate, } from "@superblocksteam/ai-service-templates/reify-template";
import { templates } from "@superblocksteam/ai-service-templates/templates";
import { generatePredictableId } from "../ids.js";
import { getErrorMeta, getLogger } from "../util/logger.js";
import llmobs from "./llmobs/index.js";
const SHARED_DIR = "__shared__";
let TemplateRenderer = (() => {
    let _instanceExtraInitializers = [];
    let _renderTemplate_decorators;
    return class TemplateRenderer {
        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
            _renderTemplate_decorators = [llmobs.decorate({ kind: "task", name: "TemplateRenderer.renderTemplate" })];
            __esDecorate(this, null, _renderTemplate_decorators, { kind: "method", name: "renderTemplate", static: false, private: false, access: { has: obj => "renderTemplate" in obj, get: obj => obj.renderTemplate }, metadata: _metadata }, null, _instanceExtraInitializers);
            if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
        }
        workDir = __runInitializers(this, _instanceExtraInitializers);
        nodeModuleDirs;
        constructor({ prefetchNodeModules = false, workDirSeed, }) {
            // Use a stable work dir derived from a caller-provided seed (typically app root).
            // This keeps template cache location deterministic for a given app context.
            const stableSeed = workDirSeed ?? process.cwd();
            const stableId = generatePredictableId(stableSeed);
            this.workDir = path.join(os.tmpdir(), `superblocks-ai-service-${stableId}`);
            this.nodeModuleDirs = {};
            if (prefetchNodeModules) {
                for (const [templateName, template] of Object.entries(templates)) {
                    getLogger().debug("[ai-service] Pre-fetching node_modules for", templateName);
                    // prefetch starts here, we don't wait for it to finish
                    this.templateModulesDir(templateName, template).catch((error) => {
                        getLogger().error(`Error pre-fetching node_modules for ${templateName}`, getErrorMeta(error));
                    });
                }
            }
        }
        async getInstallationTasks() {
            await Promise.all(Object.values(this.nodeModuleDirs));
        }
        async renderTemplate(templateName) {
            if (!(templateName in templates)) {
                throw new Error(`Template ${templateName} not found`);
            }
            const template = templates[templateName];
            const templateNodeModules = await this.templateModulesDir(templateName, template);
            const templateDir = path.join(this.workDir, templateName, `gen-${randomUUID()}`);
            getLogger().debug(`[ai-service] Rendering template ${templateName} to ${templateDir}`);
            reifyTemplate(templateDir, templates[templateName]);
            if (templateNodeModules) {
                // copy shared node_modules into the rendered template
                await fs.cp(templateNodeModules, path.join(templateDir, "node_modules"), {
                    recursive: true,
                });
            }
            return templateDir;
        }
        // resolve shared node_modules for a template
        async templateModulesDir(templateName, template) {
            if (this.nodeModuleDirs[templateName]) {
                return this.nodeModuleDirs[templateName];
            }
            if (!("package.json" in template)) {
                // no node_modules for this template
                this.nodeModuleDirs[templateName] = Promise.resolve(null);
                return this.nodeModuleDirs[templateName];
            }
            const packageJson = template["package.json"];
            const dir = path.join(this.workDir, templateName, SHARED_DIR);
            await fs.mkdir(dir, { recursive: true });
            await fs.writeFile(path.join(dir, "package.json"), packageJson, "utf-8");
            const installDfd = new Promise((resolve, reject) => {
                const child = spawn("npm", ["install", "--ignore-scripts"], {
                    cwd: dir,
                    shell: true,
                });
                let stdout = "";
                let stderr = "";
                child.stdout?.on("data", (chunk) => {
                    stdout += String(chunk);
                });
                child.stderr?.on("data", (chunk) => {
                    stderr += String(chunk);
                });
                child.on("error", (error) => {
                    reject(new Error(`npm install failed to start: ${error instanceof Error ? error.message : String(error)}`));
                });
                child.on("close", (code) => {
                    if (code !== 0) {
                        const stdoutTail = stdout.trim().split("\n").slice(-30).join("\n");
                        const stderrTail = stderr.trim().split("\n").slice(-30).join("\n");
                        reject(new Error([
                            `npm install failed with code ${code}`,
                            stdoutTail ? `stdout (tail):\n${stdoutTail}` : undefined,
                            stderrTail ? `stderr (tail):\n${stderrTail}` : undefined,
                        ]
                            .filter(Boolean)
                            .join("\n\n")));
                        return;
                    }
                    resolve(path.join(dir, "node_modules"));
                });
            });
            this.nodeModuleDirs[templateName] = installDfd;
            return installDfd;
        }
    };
})();
export { TemplateRenderer };
//# sourceMappingURL=template-renderer.js.map