"use strict";
/**
 * TypeScript Nexus web host — HTTP, templates, static files, demo DB.
 * Mirrors the Go pkg/host surface enough to run nex-registry/app/*.nex.
 */
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.isPostgres = isPostgres;
exports.resolveWebHostConfig = resolveWebHostConfig;
exports.createWebHost = createWebHost;
const crypto = __importStar(require("crypto"));
const fs = __importStar(require("fs"));
const http = __importStar(require("http"));
const path = __importStar(require("path"));
const smol_toml_1 = require("smol-toml");
const builtins_1 = require("../language/builtins");
const evaluator_1 = require("../language/evaluator");
const values_1 = require("../language/values");
const convert_1 = require("./convert");
const design_1 = require("./design");
const docs_1 = require("./docs");
const db_memory_1 = require("./db_memory");
const db_postgres_1 = require("./db_postgres");
const markdown_1 = require("./markdown");
const router_1 = require("./router");
const templates_1 = require("./templates");
function isPostgres(db) {
    return db.mode === "postgres";
}
const SESSION_COOKIE = "nex_session";
const SESSION_TTL_SEC = 30 * 24 * 60 * 60;
function cookieSecureFlag(baseUrl) {
    if (process.env.COOKIE_SECURE === "1" || process.env.COOKIE_SECURE === "true") {
        return true;
    }
    if (process.env.COOKIE_SECURE === "0" || process.env.COOKIE_SECURE === "false") {
        return false;
    }
    return baseUrl.toLowerCase().startsWith("https://");
}
function parseListenAddr(addr) {
    const trimmed = addr.trim();
    if (trimmed.startsWith(":")) {
        return { host: "0.0.0.0", port: Number.parseInt(trimmed.slice(1), 10) || 8080 };
    }
    const idx = trimmed.lastIndexOf(":");
    if (idx > 0) {
        return {
            host: trimmed.slice(0, idx),
            port: Number.parseInt(trimmed.slice(idx + 1), 10) || 8080,
        };
    }
    return { host: "0.0.0.0", port: Number.parseInt(trimmed, 10) || 8080 };
}
function parseCookies(header) {
    const out = {};
    if (!header) {
        return out;
    }
    for (const part of header.split(";")) {
        const eq = part.indexOf("=");
        if (eq < 0) {
            continue;
        }
        const k = part.slice(0, eq).trim();
        const v = part.slice(eq + 1).trim();
        out[k] = decodeURIComponent(v);
    }
    return out;
}
function parseQuery(url) {
    const out = {};
    url.searchParams.forEach((v, k) => {
        if (!(k in out)) {
            out[k] = v;
        }
    });
    return out;
}
function parseFormUrlEncoded(body) {
    const out = {};
    for (const part of body.split("&")) {
        if (!part) {
            continue;
        }
        const eq = part.indexOf("=");
        const k = decodeURIComponent((eq < 0 ? part : part.slice(0, eq)).replace(/\+/g, " "));
        const v = decodeURIComponent((eq < 0 ? "" : part.slice(eq + 1)).replace(/\+/g, " "));
        out[k] = v;
    }
    return out;
}
function parseMultipart(body, boundary) {
    const fields = {};
    const files = {};
    const sep = Buffer.from(`--${boundary}`);
    let start = body.indexOf(sep) + sep.length;
    while (start < body.length) {
        if (body[start] === 45 && body[start + 1] === 45) {
            break; // --
        }
        if (body[start] === 13 && body[start + 1] === 10) {
            start += 2;
        }
        const next = body.indexOf(sep, start);
        const chunk = body.subarray(start, next < 0 ? body.length : next - 2);
        const headerEnd = chunk.indexOf("\r\n\r\n");
        if (headerEnd < 0) {
            break;
        }
        const headerText = chunk.subarray(0, headerEnd).toString("utf8");
        const content = chunk.subarray(headerEnd + 4);
        const nameMatch = /name="([^"]+)"/.exec(headerText);
        const fileMatch = /filename="([^"]*)"/.exec(headerText);
        const ctMatch = /Content-Type:\s*([^\r\n]+)/i.exec(headerText);
        if (nameMatch) {
            const name = nameMatch[1];
            if (fileMatch) {
                files[name] = {
                    filename: fileMatch[1] || "upload",
                    data: Buffer.from(content),
                    contentType: ctMatch?.[1]?.trim() || "application/octet-stream",
                };
            }
            else {
                fields[name] = content.toString("utf8");
            }
        }
        if (next < 0) {
            break;
        }
        start = next + sep.length;
    }
    return { fields, files };
}
function loadTemplatesFromWebDir(webDir) {
    const tplDir = path.join(webDir, "templates");
    if (!fs.existsSync(tplDir)) {
        return null;
    }
    const basePath = path.join(tplDir, "base.html");
    if (!fs.existsSync(basePath)) {
        return null;
    }
    const base = fs.readFileSync(basePath, "utf8");
    const pages = {};
    for (const name of fs.readdirSync(tplDir)) {
        if (!name.endsWith(".html") || name === "base.html") {
            continue;
        }
        const full = path.join(tplDir, name);
        if (fs.statSync(full).isFile()) {
            pages[name] = fs.readFileSync(full, "utf8");
        }
    }
    const partials = {};
    const partialDir = path.join(tplDir, "partials");
    if (fs.existsSync(partialDir)) {
        for (const name of fs.readdirSync(partialDir)) {
            if (!name.endsWith(".html")) {
                continue;
            }
            partials[name] = fs.readFileSync(path.join(partialDir, name), "utf8");
        }
    }
    return (0, templates_1.loadGoTemplates)({ base, pages, partials });
}
function pbkdf2Hash(password) {
    const salt = crypto.randomBytes(16).toString("hex");
    const hash = crypto.pbkdf2Sync(password, salt, 100_000, 32, "sha256").toString("hex");
    return `pbkdf2$${salt}$${hash}`;
}
function pbkdf2Check(stored, password) {
    if (stored.startsWith("pbkdf2$")) {
        const [, salt, hash] = stored.split("$");
        if (!salt || !hash) {
            return false;
        }
        const check = crypto.pbkdf2Sync(password, salt, 100_000, 32, "sha256").toString("hex");
        return crypto.timingSafeEqual(Buffer.from(hash, "hex"), Buffer.from(check, "hex"));
    }
    // bcrypt hashes from Go host — accept only exact demo match
    return false;
}
function resolveWebHostConfig(entryFile, cwd = process.cwd()) {
    const absFile = path.resolve(entryFile);
    const appDir = path.resolve(process.env.NEX_APP_DIR || path.dirname(absFile));
    let webDir = process.env.NEX_WEB_DIR || "";
    if (!webDir) {
        const candidates = [
            path.join(cwd, "web"),
            path.join(path.dirname(appDir), "web"),
            path.join(appDir, "..", "web"),
        ];
        for (const c of candidates) {
            const abs = path.resolve(c);
            if (fs.existsSync(path.join(abs, "templates"))) {
                webDir = abs;
                break;
            }
        }
    }
    else {
        webDir = path.resolve(webDir);
    }
    const storageDir = path.resolve(process.env.STORAGE_DIR || path.join(path.dirname(appDir), "storage"));
    const listenAddr = process.env.LISTEN_ADDR || ":8080";
    const baseUrl = (process.env.BASE_URL || "http://localhost:8080").replace(/\/$/, "");
    const cdnBaseUrl = (process.env.CDN_BASE_URL || baseUrl).replace(/\/$/, "");
    const maxUploadBytes = Number.parseInt(process.env.MAX_UPLOAD_BYTES || "", 10) || 64 << 20;
    const databaseUrl = (process.env.DATABASE_URL || "").trim() || undefined;
    const migrationsDir = process.env.MIGRATIONS_DIR ||
        path.join(path.dirname(appDir), "migrations");
    return {
        storageDir,
        baseUrl,
        cdnBaseUrl,
        listenAddr,
        maxUploadBytes,
        appDir,
        webDir,
        databaseUrl,
        migrationsDir,
    };
}
function createWebHost(config) {
    const router = new router_1.Router();
    let db;
    let dbMode;
    if (config.databaseUrl) {
        try {
            const pg = new db_postgres_1.PostgresDB(config.databaseUrl);
            if (config.migrationsDir) {
                pg.applyMigrations(config.migrationsDir);
            }
            pg.seedFromStorage(config.storageDir);
            db = pg;
            dbMode = "postgres";
            // eslint-disable-next-line no-console
            console.log(`[host] Postgres DB (${pg.packages.length} packages, ${pg.versions.length} versions)`);
        }
        catch (e) {
            // Fall back to the in-memory store rather than serving a broken site.
            // eslint-disable-next-line no-console
            console.error(`[host] Postgres init failed (${e instanceof Error ? e.message : String(e)}); falling back to in-memory demo DB`);
            const mem = new db_memory_1.MemoryDB();
            mem.seedFromStorage(config.storageDir);
            db = mem;
            dbMode = "memory";
        }
    }
    else {
        const mem = new db_memory_1.MemoryDB();
        mem.seedFromStorage(config.storageDir);
        db = mem;
        dbMode = "memory";
        // eslint-disable-next-line no-console
        console.log("[host] in-memory demo DB (seeded from STORAGE_DIR)");
    }
    const secureCookies = cookieSecureFlag(config.baseUrl);
    const forms = new Map();
    let templates = config.webDir ? loadTemplatesFromWebDir(config.webDir) : null;
    let server = null;
    let applyFn = () => new values_1.ErrorObj("host applyFunction not set");
    const host = {
        config,
        router,
        db,
        dbMode,
        get routeCount() {
            return router.routeCount;
        },
        install(env, apply) {
            applyFn = apply;
            installBuiltins(env);
        },
        async listen() {
            await listenAndServe();
        },
        async close() {
            await new Promise((resolve, reject) => {
                if (!server) {
                    resolve();
                    return;
                }
                server.close((err) => (err ? reject(err) : resolve()));
            });
            if (isPostgres(db)) {
                db.close();
            }
        },
    };
    function setBuiltin(env, name, fn) {
        env.set(name, new builtins_1.BuiltinObj(fn));
    }
    function routeBuiltin(method) {
        return (...args) => {
            const err = (0, convert_1.expectArgs)(`http_${method.toLowerCase()}`, 2, args);
            if (err) {
                return err;
            }
            const routePath = (0, convert_1.asString)(args[0]);
            const fn = args[1];
            if (!routePath || !(fn instanceof evaluator_1.FunctionObj)) {
                return new values_1.ErrorObj(`http_${method.toLowerCase()} expects (path, function)`);
            }
            router.add(method, routePath, async (req) => {
                const reqObj = requestToHash(req);
                return applyFn(fn, [reqObj]);
            });
            return values_1.NULL_OBJ;
        };
    }
    function requestToHash(req) {
        const h = new values_1.HashObj();
        h.setString("method", new values_1.StringObj(req.method));
        h.setString("path", new values_1.StringObj(req.path));
        h.setString("request_id", new values_1.StringObj(req.requestId));
        h.setString("auth_via", new values_1.StringObj(req.authVia));
        h.setString("query", (0, convert_1.fromJs)(req.query));
        h.setString("headers", (0, convert_1.fromJs)(req.headers));
        h.setString("params", (0, convert_1.fromJs)(req.params));
        h.setString("cookies", (0, convert_1.fromJs)(req.cookies));
        h.setString("form", (0, convert_1.fromJs)(req.form));
        h.setString("body", new values_1.StringObj(req.body));
        h.setString("user", req.user ? (0, convert_1.fromJs)(req.user) : values_1.NULL_OBJ);
        h.setString("api_key_scope", new values_1.StringObj(""));
        h.setString("api_key_id", values_1.NULL_OBJ);
        return h;
    }
    function writeResponse(res, _req, result, user) {
        if (!(result instanceof values_1.HashObj)) {
            res.statusCode = 200;
            res.setHeader("Content-Type", "application/json; charset=utf-8");
            res.end(JSON.stringify({ result: (0, convert_1.toJs)(result) }));
            return;
        }
        const setCookie = result.getString("set_cookie");
        if (setCookie instanceof values_1.HashObj) {
            const name = (0, convert_1.hashGetString)(setCookie, "name");
            const value = (0, convert_1.hashGetString)(setCookie, "value");
            const maxAge = (0, convert_1.asInt)(setCookie.getString("max_age")) ?? SESSION_TTL_SEC;
            const secure = secureCookies ? "; Secure" : "";
            res.setHeader("Set-Cookie", `${name}=${encodeURIComponent(value)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${maxAge}${secure}`);
        }
        const clearCookie = result.getString("clear_cookie");
        if (clearCookie instanceof values_1.StringObj) {
            const secure = secureCookies ? "; Secure" : "";
            res.setHeader("Set-Cookie", `${clearCookie.value}=; Path=/; HttpOnly; Max-Age=0${secure}`);
        }
        const status = (0, convert_1.asInt)(result.getString("status")) ?? 200;
        const redir = result.getString("redirect");
        if (redir instanceof values_1.StringObj) {
            res.statusCode = status;
            res.setHeader("Location", redir.value);
            res.end();
            return;
        }
        const file = result.getString("file");
        if (file instanceof values_1.StringObj) {
            const filename = (0, convert_1.hashGetString)(result, "filename");
            const ct = (0, convert_1.hashGetString)(result, "content_type") || "application/octet-stream";
            if (!fs.existsSync(file.value)) {
                res.statusCode = 404;
                res.setHeader("Content-Type", "application/json");
                res.end(JSON.stringify({ error: "file not found" }));
                return;
            }
            res.statusCode = status;
            res.setHeader("Content-Type", ct);
            if (filename) {
                res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
            }
            fs.createReadStream(file.value).pipe(res);
            return;
        }
        const htmlPage = result.getString("html");
        if (htmlPage instanceof values_1.StringObj) {
            const dataRaw = result.getString("data");
            const data = dataRaw instanceof values_1.HashObj
                ? (0, convert_1.toJs)(dataRaw)
                : {};
            if (user) {
                data.CurrentUser = user;
            }
            data.GitHubAuthEnabled = Boolean(process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET);
            try {
                if (!templates && config.webDir) {
                    templates = loadTemplatesFromWebDir(config.webDir);
                }
                if (!templates) {
                    throw new Error("templates not loaded (set NEX_WEB_DIR)");
                }
                const body = templates.render(htmlPage.value, (0, templates_1.normalizeTemplateData)(data));
                res.statusCode = status;
                res.setHeader("Content-Type", "text/html; charset=utf-8");
                res.end(body);
            }
            catch (err) {
                // eslint-disable-next-line no-console
                console.error("template error", err);
                res.statusCode = 500;
                res.end("template error");
            }
            return;
        }
        const jsonPayload = result.getString("json");
        if (jsonPayload.type !== "NULL") {
            res.statusCode = status;
            res.setHeader("Content-Type", "application/json; charset=utf-8");
            res.end(JSON.stringify((0, convert_1.toJs)(jsonPayload)));
            return;
        }
        const body = result.getString("body");
        if (body.type !== "NULL") {
            const ct = (0, convert_1.hashGetString)(result, "content_type") || "text/plain; charset=utf-8";
            res.statusCode = status;
            res.setHeader("Content-Type", ct);
            const text = body instanceof values_1.StringObj ? body.value : body.inspect();
            res.end(text);
            return;
        }
        res.statusCode = status;
        res.setHeader("Content-Type", "application/json; charset=utf-8");
        res.end(JSON.stringify((0, convert_1.toJs)(result)));
    }
    function resolveUser(req) {
        const auth = req.headers.authorization;
        if (auth && auth.toLowerCase().startsWith("bearer ")) {
            const token = auth.slice(7).trim();
            const u = db.userFromSession(token);
            if (u) {
                return { user: u, via: "bearer" };
            }
        }
        const cookies = parseCookies(req.headers.cookie);
        const sess = cookies[SESSION_COOKIE];
        if (sess) {
            const u = db.userFromSession(sess);
            if (u) {
                return { user: u, via: "cookie" };
            }
        }
        return { user: null, via: "" };
    }
    function installBuiltins(env) {
        setBuiltin(env, "env", (...args) => {
            const err = (0, convert_1.expectArgs)("env", 1, args);
            if (err) {
                return err;
            }
            const key = (0, convert_1.asString)(args[0]);
            if (!key) {
                return new values_1.ErrorObj("env expects string");
            }
            return new values_1.StringObj(process.env[key] ?? "");
        });
        setBuiltin(env, "config", () => (0, convert_1.fromJs)({
            storage_dir: config.storageDir,
            base_url: config.baseUrl,
            cdn_base_url: config.cdnBaseUrl,
            listen_addr: config.listenAddr,
            max_upload_bytes: config.maxUploadBytes,
            app_dir: config.appDir,
            web_dir: config.webDir,
            mode: dbMode,
            db_mode: dbMode,
            database_url_set: Boolean(config.databaseUrl),
        }));
        setBuiltin(env, "http_get", routeBuiltin("GET"));
        setBuiltin(env, "http_post", routeBuiltin("POST"));
        setBuiltin(env, "http_put", routeBuiltin("PUT"));
        setBuiltin(env, "http_patch", routeBuiltin("PATCH"));
        setBuiltin(env, "http_delete", routeBuiltin("DELETE"));
        setBuiltin(env, "http_not_found", (...args) => {
            const err = (0, convert_1.expectArgs)("http_not_found", 1, args);
            if (err) {
                return err;
            }
            const fn = args[0];
            if (!(fn instanceof evaluator_1.FunctionObj)) {
                return new values_1.ErrorObj("http_not_found expects function");
            }
            router.notFoundHandler = async (req) => applyFn(fn, [requestToHash(req)]);
            return values_1.NULL_OBJ;
        });
        setBuiltin(env, "json", (...args) => {
            const err = (0, convert_1.expectMinArgs)("json", 1, args);
            if (err) {
                return err;
            }
            let status = 200;
            let payload = args[0];
            if (args.length === 2) {
                const s = (0, convert_1.asInt)(args[0]);
                if (s !== null) {
                    status = s;
                    payload = args[1];
                }
            }
            const out = new values_1.HashObj();
            out.setString("status", new values_1.IntegerObj(status));
            out.setString("json", payload);
            return out;
        });
        setBuiltin(env, "html", (...args) => {
            const err = (0, convert_1.expectMinArgs)("html", 2, args);
            if (err) {
                return err;
            }
            let status = 200;
            let pageIdx = 0;
            let dataIdx = 1;
            if (args.length === 3) {
                const s = (0, convert_1.asInt)(args[0]);
                if (s !== null) {
                    status = s;
                    pageIdx = 1;
                    dataIdx = 2;
                }
            }
            const page = (0, convert_1.asString)(args[pageIdx]);
            if (!page) {
                return new values_1.ErrorObj("html page must be string");
            }
            const out = new values_1.HashObj();
            out.setString("status", new values_1.IntegerObj(status));
            out.setString("html", new values_1.StringObj(page));
            out.setString("data", args[dataIdx]);
            return out;
        });
        /** Return a raw HTML document body (no Go templates). */
        setBuiltin(env, "html_doc", (...args) => {
            const err = (0, convert_1.expectMinArgs)("html_doc", 1, args);
            if (err) {
                return err;
            }
            let status = 200;
            let bodyIdx = 0;
            if (args.length >= 2) {
                const s = (0, convert_1.asInt)(args[0]);
                if (s !== null) {
                    status = s;
                    bodyIdx = 1;
                }
            }
            const body = (0, convert_1.asString)(args[bodyIdx]);
            if (body === null) {
                return new values_1.ErrorObj("html_doc expects an HTML string");
            }
            const out = new values_1.HashObj();
            out.setString("status", new values_1.IntegerObj(status));
            out.setString("body", new values_1.StringObj(body));
            out.setString("content_type", new values_1.StringObj("text/html; charset=utf-8"));
            return out;
        });
        /** Render a design tree (hash) to an HTML fragment string. */
        setBuiltin(env, "design_render", (...args) => {
            const err = (0, convert_1.expectMinArgs)("design_render", 1, args);
            if (err) {
                return err;
            }
            const tree = (0, convert_1.toJs)(args[0]);
            const theme = args.length >= 2 ? (0, convert_1.toJs)(args[1]) : undefined;
            try {
                return new values_1.StringObj((0, design_1.renderDesignTree)(tree, theme));
            }
            catch (e) {
                return new values_1.ErrorObj(`design_render: ${e instanceof Error ? e.message : String(e)}`);
            }
        });
        /** Render a design page tree to a full HTML document string. */
        setBuiltin(env, "design_document", (...args) => {
            const err = (0, convert_1.expectArgs)("design_document", 1, args);
            if (err) {
                return err;
            }
            try {
                return new values_1.StringObj((0, design_1.renderDesignDocument)((0, convert_1.toJs)(args[0])));
            }
            catch (e) {
                return new values_1.ErrorObj(`design_document: ${e instanceof Error ? e.message : String(e)}`);
            }
        });
        /** HTTP response for a design page (full document). */
        setBuiltin(env, "design_response", (...args) => {
            const err = (0, convert_1.expectMinArgs)("design_response", 1, args);
            if (err) {
                return err;
            }
            let status = 200;
            let pageIdx = 0;
            if (args.length >= 2) {
                const s = (0, convert_1.asInt)(args[0]);
                if (s !== null) {
                    status = s;
                    pageIdx = 1;
                }
            }
            try {
                const doc = (0, design_1.renderDesignDocument)((0, convert_1.toJs)(args[pageIdx]));
                const out = new values_1.HashObj();
                out.setString("status", new values_1.IntegerObj(status));
                out.setString("body", new values_1.StringObj(doc));
                out.setString("content_type", new values_1.StringObj("text/html; charset=utf-8"));
                return out;
            }
            catch (e) {
                return new values_1.ErrorObj(`design_response: ${e instanceof Error ? e.message : String(e)}`);
            }
        });
        /** Emit CSS custom properties + structural rules for a theme. */
        setBuiltin(env, "design_css", (...args) => {
            const err = (0, convert_1.expectArgs)("design_css", 1, args);
            if (err) {
                return err;
            }
            try {
                return new values_1.StringObj((0, design_1.themeToCss)((0, design_1.themeFromData)((0, convert_1.toJs)(args[0]))));
            }
            catch (e) {
                return new values_1.ErrorObj(`design_css: ${e instanceof Error ? e.message : String(e)}`);
            }
        });
        setBuiltin(env, "redirect", (...args) => {
            const err = (0, convert_1.expectArgs)("redirect", 1, args);
            if (err) {
                return err;
            }
            const url = (0, convert_1.asString)(args[0]);
            if (!url) {
                return new values_1.ErrorObj("redirect expects string");
            }
            const out = new values_1.HashObj();
            out.setString("status", new values_1.IntegerObj(303));
            out.setString("redirect", new values_1.StringObj(url));
            return out;
        });
        setBuiltin(env, "file_response", (...args) => {
            const err = (0, convert_1.expectMinArgs)("file_response", 2, args);
            if (err) {
                return err;
            }
            const filePath = (0, convert_1.asString)(args[0]);
            const name = (0, convert_1.asString)(args[1]);
            if (!filePath || !name) {
                return new values_1.ErrorObj("file_response expects (path, filename [, content_type])");
            }
            const ct = (0, convert_1.asString)(args[2]) || "application/octet-stream";
            const out = new values_1.HashObj();
            out.setString("status", new values_1.IntegerObj(200));
            out.setString("file", new values_1.StringObj(filePath));
            out.setString("filename", new values_1.StringObj(name));
            out.setString("content_type", new values_1.StringObj(ct));
            return out;
        });
        setBuiltin(env, "with_cookie", (...args) => {
            const err = (0, convert_1.expectArgs)("with_cookie", 3, args);
            if (err) {
                return err;
            }
            const resp = args[0];
            if (!(resp instanceof values_1.HashObj)) {
                return new values_1.ErrorObj("with_cookie expects response hash");
            }
            const name = (0, convert_1.asString)(args[1]);
            const value = (0, convert_1.asString)(args[2]);
            if (!name || value === null) {
                return new values_1.ErrorObj("cookie name/value must be strings");
            }
            const cookie = new values_1.HashObj();
            cookie.setString("name", new values_1.StringObj(name));
            cookie.setString("value", new values_1.StringObj(value));
            cookie.setString("max_age", new values_1.IntegerObj(SESSION_TTL_SEC));
            resp.setString("set_cookie", cookie);
            return resp;
        });
        setBuiltin(env, "clear_cookie", (...args) => {
            const err = (0, convert_1.expectArgs)("clear_cookie", 2, args);
            if (err) {
                return err;
            }
            const resp = args[0];
            if (!(resp instanceof values_1.HashObj)) {
                return new values_1.ErrorObj("clear_cookie expects response hash");
            }
            const name = (0, convert_1.asString)(args[1]);
            if (!name) {
                return new values_1.ErrorObj("cookie name must be string");
            }
            resp.setString("clear_cookie", new values_1.StringObj(name));
            return resp;
        });
        setBuiltin(env, "json_parse", (...args) => {
            const err = (0, convert_1.expectArgs)("json_parse", 1, args);
            if (err) {
                return err;
            }
            const s = (0, convert_1.asString)(args[0]);
            if (s === null) {
                return new values_1.ErrorObj("json_parse expects string");
            }
            try {
                return (0, convert_1.fromJs)(JSON.parse(s));
            }
            catch (e) {
                return new values_1.ErrorObj(`json_parse: ${e instanceof Error ? e.message : String(e)}`);
            }
        });
        setBuiltin(env, "json_stringify", (...args) => {
            const err = (0, convert_1.expectArgs)("json_stringify", 1, args);
            if (err) {
                return err;
            }
            try {
                return new values_1.StringObj(JSON.stringify((0, convert_1.toJs)(args[0])));
            }
            catch (e) {
                return new values_1.ErrorObj(`json_stringify: ${e instanceof Error ? e.message : String(e)}`);
            }
        });
        setBuiltin(env, "toml_parse", (...args) => {
            const err = (0, convert_1.expectArgs)("toml_parse", 1, args);
            if (err) {
                return err;
            }
            const s = (0, convert_1.asString)(args[0]);
            if (s === null) {
                return new values_1.ErrorObj("toml_parse expects string");
            }
            try {
                return (0, convert_1.fromJs)((0, smol_toml_1.parse)(s));
            }
            catch (e) {
                return new values_1.ErrorObj(`toml_parse: ${e instanceof Error ? e.message : String(e)}`);
            }
        });
        setBuiltin(env, "sha256", (...args) => {
            const err = (0, convert_1.expectArgs)("sha256", 1, args);
            if (err) {
                return err;
            }
            const s = (0, convert_1.asString)(args[0]);
            if (s === null) {
                return new values_1.ErrorObj("sha256 expects string");
            }
            return new values_1.StringObj(crypto.createHash("sha256").update(s).digest("hex"));
        });
        setBuiltin(env, "sha256_bytes", (...args) => {
            const err = (0, convert_1.expectArgs)("sha256_bytes", 1, args);
            if (err) {
                return err;
            }
            const s = (0, convert_1.asString)(args[0]);
            if (s === null) {
                return new values_1.ErrorObj("sha256_bytes expects string");
            }
            return new values_1.StringObj("sha256:" + crypto.createHash("sha256").update(s).digest("hex"));
        });
        setBuiltin(env, "bcrypt_hash", (...args) => {
            const err = (0, convert_1.expectArgs)("bcrypt_hash", 1, args);
            if (err) {
                return err;
            }
            const s = (0, convert_1.asString)(args[0]);
            if (s === null) {
                return new values_1.ErrorObj("bcrypt_hash expects string");
            }
            return new values_1.StringObj(pbkdf2Hash(s));
        });
        setBuiltin(env, "bcrypt_check", (...args) => {
            const err = (0, convert_1.expectArgs)("bcrypt_check", 2, args);
            if (err) {
                return err;
            }
            const hash = (0, convert_1.asString)(args[0]);
            const pw = (0, convert_1.asString)(args[1]);
            if (hash === null || pw === null) {
                return new values_1.ErrorObj("bcrypt_check expects (hash, password)");
            }
            return pbkdf2Check(hash, pw) ? values_1.TRUE_OBJ : values_1.FALSE_OBJ;
        });
        setBuiltin(env, "random_hex", (...args) => {
            let n = 32;
            if (args.length === 1) {
                n = (0, convert_1.asInt)(args[0]) ?? 32;
            }
            return new values_1.StringObj(crypto.randomBytes(n).toString("hex"));
        });
        setBuiltin(env, "gravatar_url", (...args) => {
            const err = (0, convert_1.expectArgs)("gravatar_url", 1, args);
            if (err) {
                return err;
            }
            const email = ((0, convert_1.asString)(args[0]) || "").trim().toLowerCase();
            const sum = crypto.createHash("sha256").update(email).digest("hex");
            return new values_1.StringObj(`https://www.gravatar.com/avatar/${sum}?d=identicon&s=160`);
        });
        setBuiltin(env, "path_join", (...args) => {
            const parts = [];
            for (const a of args) {
                const s = (0, convert_1.asString)(a);
                if (s === null) {
                    return new values_1.ErrorObj("path_join expects strings");
                }
                parts.push(s);
            }
            return new values_1.StringObj(path.join(...parts));
        });
        setBuiltin(env, "re_match", (...args) => {
            const err = (0, convert_1.expectArgs)("re_match", 2, args);
            if (err) {
                return err;
            }
            const pat = (0, convert_1.asString)(args[0]);
            const s = (0, convert_1.asString)(args[1]);
            if (pat === null || s === null) {
                return new values_1.ErrorObj("re_match expects (pattern, string)");
            }
            try {
                return new RegExp(pat).test(s) ? values_1.TRUE_OBJ : values_1.FALSE_OBJ;
            }
            catch (e) {
                return new values_1.ErrorObj(e instanceof Error ? e.message : String(e));
            }
        });
        setBuiltin(env, "markdown_html", (...args) => {
            const err = (0, convert_1.expectArgs)("markdown_html", 1, args);
            if (err) {
                return err;
            }
            const src = (0, convert_1.asString)(args[0]);
            if (src === null) {
                return new values_1.ErrorObj("markdown_html expects string");
            }
            return new values_1.StringObj((0, markdown_1.markdownToHtml)(src));
        });
        setBuiltin(env, "docs_get", (...args) => {
            let id = "overview";
            if (args.length > 0) {
                const s = (0, convert_1.asString)(args[0]);
                if (s) {
                    id = s;
                }
            }
            const page = docs_1.DOCS_PAGES[id];
            if (!page) {
                return values_1.NULL_OBJ;
            }
            return (0, convert_1.fromJs)({
                id,
                Title: page.Title,
                Lead: page.Lead,
                Section: page.Section,
                Body: page.Body,
            });
        });
        setBuiltin(env, "multipart_text", (...args) => {
            const err = (0, convert_1.expectArgs)("multipart_text", 2, args);
            if (err) {
                return err;
            }
            const reqHash = args[0];
            const field = (0, convert_1.asString)(args[1]);
            if (!(reqHash instanceof values_1.HashObj) || !field) {
                return new values_1.ErrorObj("multipart_text expects (req, field)");
            }
            const rid = (0, convert_1.hashGetString)(reqHash, "request_id");
            const form = forms.get(rid);
            return new values_1.StringObj(form?.fields[field] ?? "");
        });
        setBuiltin(env, "multipart_file", (...args) => {
            const err = (0, convert_1.expectArgs)("multipart_file", 2, args);
            if (err) {
                return err;
            }
            const reqHash = args[0];
            const field = (0, convert_1.asString)(args[1]);
            if (!(reqHash instanceof values_1.HashObj) || !field) {
                return new values_1.ErrorObj("multipart_file expects (req, field)");
            }
            const rid = (0, convert_1.hashGetString)(reqHash, "request_id");
            const form = forms.get(rid);
            const file = form?.files[field];
            if (!file) {
                return values_1.NULL_OBJ;
            }
            const sum = crypto.createHash("sha256").update(file.data).digest("hex");
            return (0, convert_1.fromJs)({
                filename: file.filename,
                size: file.data.length,
                data: file.data.toString("binary"),
                sha256: "sha256:" + sum,
            });
        });
        setBuiltin(env, "send_email", (...args) => {
            const err = (0, convert_1.expectArgs)("send_email", 3, args);
            if (err) {
                return err;
            }
            // eslint-disable-next-line no-console
            console.log(`[email] to=${(0, convert_1.asString)(args[0])} subject=${(0, convert_1.asString)(args[1])}\n${(0, convert_1.asString)(args[2])}`);
            return values_1.TRUE_OBJ;
        });
        installDbBuiltins(env);
    }
    function installDbBuiltins(env) {
        const stubErr = (name) => new values_1.ErrorObj(`${name}: not available in memory demo mode`);
        setBuiltin(env, "db_mode", () => new values_1.StringObj(dbMode));
        setBuiltin(env, "db_ping", () => values_1.TRUE_OBJ);
        setBuiltin(env, "db_count_packages", () => new values_1.IntegerObj(isPostgres(db)
            ? Number(db.hubStats().Packages ?? 0)
            : db.packages.length));
        setBuiltin(env, "db_count_versions", () => new values_1.IntegerObj(isPostgres(db)
            ? Number(db.hubStats().Versions ?? 0)
            : db.versions.length));
        setBuiltin(env, "db_count_users", () => new values_1.IntegerObj(isPostgres(db)
            ? Math.max(Number(db.hubStats().Users ?? 0), 1)
            : Math.max(db.users.length, 1)));
        setBuiltin(env, "db_sum_downloads", () => new values_1.IntegerObj(isPostgres(db)
            ? Number(db.hubStats().Downloads ?? 0)
            : db.packages.reduce((a, p) => a + p.DownloadCount, 0)));
        setBuiltin(env, "db_top_tags", (...args) => {
            const limit = (0, convert_1.asInt)(args[0]) ?? 16;
            return (0, convert_1.fromJs)(db.topTags(limit));
        });
        setBuiltin(env, "db_hub_stats", () => (0, convert_1.fromJs)(db.hubStats()));
        setBuiltin(env, "db_list_recent", (...args) => {
            const limit = (0, convert_1.asInt)(args[0]) ?? 10;
            return (0, convert_1.fromJs)(db.listRecent(limit));
        });
        setBuiltin(env, "db_list_popular", (...args) => {
            const limit = (0, convert_1.asInt)(args[0]) ?? 10;
            return (0, convert_1.fromJs)(db.listPopular(limit));
        });
        setBuiltin(env, "db_search", (...args) => {
            if (args.length < 1) {
                return new values_1.ErrorObj("db_search wants opts hash");
            }
            if (args[0] instanceof values_1.HashObj) {
                return (0, convert_1.fromJs)(db.search((0, convert_1.toJs)(args[0])));
            }
            const q = (0, convert_1.asString)(args[0]) || "";
            return (0, convert_1.fromJs)(db.search({ q, browse: true }).packages);
        });
        setBuiltin(env, "db_list_licenses", (...args) => (0, convert_1.fromJs)(db.listLicenses((0, convert_1.asInt)(args[0]) ?? 20)));
        setBuiltin(env, "db_top_keywords", (...args) => (0, convert_1.fromJs)(db.topKeywords((0, convert_1.asInt)(args[0]) ?? 24)));
        setBuiltin(env, "db_list_packages_page", (...args) => {
            const page = (0, convert_1.asInt)(args[0]) ?? 1;
            const per = (0, convert_1.asInt)(args[1]) ?? 25;
            const res = db.search({ browse: true, sort: "downloads", limit: per, offset: (page - 1) * per });
            return (0, convert_1.fromJs)({
                packages: res.packages,
                page: { Page: page, PerPage: per, Total: res.total, HasNext: page * per < res.total, HasPrev: page > 1 },
            });
        });
        setBuiltin(env, "db_get_package", (...args) => {
            const name = (0, convert_1.asString)(args[0]);
            if (!name) {
                return values_1.NULL_OBJ;
            }
            const pkg = db.getPackage(name);
            return pkg ? (0, convert_1.fromJs)(pkg) : values_1.NULL_OBJ;
        });
        setBuiltin(env, "db_list_versions", (...args) => {
            const id = (0, convert_1.asInt)(args[0]);
            if (id === null) {
                return (0, convert_1.fromJs)([]);
            }
            return (0, convert_1.fromJs)(db.listVersions(id));
        });
        setBuiltin(env, "db_get_package_version", (...args) => {
            const name = (0, convert_1.asString)(args[0]);
            const ver = (0, convert_1.asString)(args[1]);
            if (!name || !ver) {
                return values_1.NULL_OBJ;
            }
            const pv = db.getPackageVersion(name, ver);
            return pv ? (0, convert_1.fromJs)(pv) : values_1.NULL_OBJ;
        });
        setBuiltin(env, "db_list_deps", () => (0, convert_1.fromJs)([]));
        setBuiltin(env, "db_list_reverse_deps", () => (0, convert_1.fromJs)([]));
        setBuiltin(env, "db_list_daily_downloads", (...args) => {
            const days = (0, convert_1.asInt)(args[1]) ?? 30;
            const out = [];
            const now = Date.now();
            for (let i = days - 1; i >= 0; i--) {
                const d = new Date(now - i * 86400000).toISOString().slice(0, 10);
                out.push({ Date: d, Count: Math.max(0, 3 - (i % 5)) });
            }
            return (0, convert_1.fromJs)(out);
        });
        setBuiltin(env, "db_increment_downloads", (...args) => {
            const id = (0, convert_1.asInt)(args[0]);
            if (id === null) {
                return values_1.TRUE_OBJ;
            }
            if (isPostgres(db)) {
                db.incrementDownloads(id);
                return values_1.TRUE_OBJ;
            }
            const pkg = db.packages.find((p) => p.ID === id);
            if (pkg) {
                pkg.DownloadCount += 1;
            }
            return values_1.TRUE_OBJ;
        });
        setBuiltin(env, "db_get_user", (...args) => {
            const u = db.getUser((0, convert_1.asString)(args[0]) || "");
            return u ? (0, convert_1.fromJs)(u) : values_1.NULL_OBJ;
        });
        setBuiltin(env, "db_get_user_by_login", (...args) => {
            const u = db.getUser((0, convert_1.asString)(args[0]) || "");
            return u ? (0, convert_1.fromJs)(u) : values_1.NULL_OBJ;
        });
        setBuiltin(env, "db_get_user_by_email", (...args) => {
            const email = ((0, convert_1.asString)(args[0]) || "").toLowerCase();
            if (isPostgres(db)) {
                const found = db.getUser(email);
                return found ? (0, convert_1.fromJs)(found) : values_1.NULL_OBJ;
            }
            const u = db.users.find((x) => x.email.toLowerCase() === email);
            return u ? (0, convert_1.fromJs)(u) : values_1.NULL_OBJ;
        });
        setBuiltin(env, "db_create_user", (...args) => {
            const username = (0, convert_1.asString)(args[0]) || "";
            const email = (0, convert_1.asString)(args[1]) || "";
            const hash = (0, convert_1.asString)(args[2]) || "";
            if (!username || !email) {
                return new values_1.ErrorObj("db_create_user: username and email required");
            }
            try {
                if (db.getUser(username) || db.users.some((u) => u.email === email)) {
                    return new values_1.ErrorObj("conflict: user already exists");
                }
                return (0, convert_1.fromJs)(db.createUser(username, email, hash, (0, convert_1.asString)(args[3]) || "", (0, convert_1.asString)(args[4]) || "", (0, convert_1.asBool)(args[5])));
            }
            catch (e) {
                return new values_1.ErrorObj(e instanceof Error ? e.message : String(e));
            }
        });
        setBuiltin(env, "db_create_session", (...args) => {
            const id = (0, convert_1.asInt)(args[0]);
            if (id === null) {
                return new values_1.ErrorObj("db_create_session wants user id");
            }
            return new values_1.StringObj(db.createSession(id));
        });
        setBuiltin(env, "db_delete_session", (...args) => {
            db.deleteSession((0, convert_1.asString)(args[0]) || "");
            return values_1.TRUE_OBJ;
        });
        setBuiltin(env, "db_user_stats", (...args) => {
            const userId = (0, convert_1.asInt)(args[0]);
            if (isPostgres(db) && userId !== null) {
                return (0, convert_1.fromJs)(db.userStats(userId));
            }
            return (0, convert_1.fromJs)({
                PackageCount: db.packages.length,
                VersionCount: db.versions.length,
                TotalDownloads: db.packages.reduce((a, p) => a + p.DownloadCount, 0),
                APIKeyCount: 0,
                TrustedCount: 0,
                package_count: db.packages.length,
                version_count: db.versions.length,
                total_downloads: db.packages.reduce((a, p) => a + p.DownloadCount, 0),
            });
        });
        setBuiltin(env, "db_list_packages_by_owner", (...args) => {
            const userId = (0, convert_1.asInt)(args[0]);
            if (isPostgres(db) && userId !== null) {
                return (0, convert_1.fromJs)(db.listPackagesByOwner(userId));
            }
            return (0, convert_1.fromJs)(db.listRecent(50));
        });
        setBuiltin(env, "db_list_user_activity", () => (0, convert_1.fromJs)([]));
        setBuiltin(env, "db_list_user_orgs", (...args) => {
            const userId = (0, convert_1.asInt)(args[0]);
            if (isPostgres(db) && userId !== null) {
                return (0, convert_1.fromJs)(db.listUserOrgs(userId));
            }
            return (0, convert_1.fromJs)([]);
        });
        setBuiltin(env, "db_list_api_keys", (...args) => {
            const userId = (0, convert_1.asInt)(args[0]);
            if (isPostgres(db) && userId !== null) {
                return (0, convert_1.fromJs)(db.listApiKeys(userId));
            }
            return (0, convert_1.fromJs)([]);
        });
        setBuiltin(env, "db_list_trusted", (...args) => {
            const userId = (0, convert_1.asInt)(args[0]);
            if (isPostgres(db) && userId !== null) {
                return (0, convert_1.fromJs)(db.listTrusted(userId));
            }
            return (0, convert_1.fromJs)([]);
        });
        setBuiltin(env, "db_list_audit_logs", () => (0, convert_1.fromJs)([]));
        setBuiltin(env, "db_list_audit_logs_admin", () => (0, convert_1.fromJs)([]));
        setBuiltin(env, "db_list_abuse_reports", () => (0, convert_1.fromJs)(db.reports));
        setBuiltin(env, "db_list_package_owners", (...args) => {
            const pkgId = (0, convert_1.asInt)(args[0]);
            if (isPostgres(db) && pkgId !== null) {
                return (0, convert_1.fromJs)(db.listPackageOwners(pkgId));
            }
            return (0, convert_1.fromJs)([]);
        });
        setBuiltin(env, "db_package_owner_role", (...args) => {
            const pkgId = (0, convert_1.asInt)(args[0]);
            const userId = (0, convert_1.asInt)(args[1]);
            if (isPostgres(db) && pkgId !== null && userId !== null) {
                return new values_1.StringObj(db.packageOwnerRole(pkgId, userId));
            }
            return new values_1.StringObj("");
        });
        setBuiltin(env, "db_user_can_publish", () => values_1.TRUE_OBJ);
        setBuiltin(env, "db_update_profile", (...args) => {
            const id = (0, convert_1.asInt)(args[0]);
            if (id === null) {
                return new values_1.ErrorObj("user not found");
            }
            if (isPostgres(db)) {
                const updated = db.updateProfile(id, (0, convert_1.asString)(args[1]) || "", (0, convert_1.asString)(args[2]) || "", (0, convert_1.asBool)(args[3]));
                return updated ? (0, convert_1.fromJs)(updated) : new values_1.ErrorObj("user not found");
            }
            const u = db.getUserById(id);
            if (!u) {
                return new values_1.ErrorObj("user not found");
            }
            u.bio = (0, convert_1.asString)(args[1]) || "";
            u.Bio = u.bio;
            u.avatar_url = (0, convert_1.asString)(args[2]) || "";
            u.AvatarURL = u.avatar_url;
            u.use_gravatar = (0, convert_1.asBool)(args[3]);
            u.UseGravatar = u.use_gravatar;
            return (0, convert_1.fromJs)(u);
        });
        setBuiltin(env, "db_unlink_github", () => values_1.TRUE_OBJ);
        setBuiltin(env, "db_create_api_key", (...args) => {
            const userId = (0, convert_1.asInt)(args[0]);
            if (isPostgres(db) && userId !== null) {
                return (0, convert_1.fromJs)(db.createApiKey(userId, (0, convert_1.asString)(args[1]) || "api key", (0, convert_1.asString)(args[2]) || undefined, (0, convert_1.asInt)(args[3]) ?? undefined));
            }
            return stubErr("db_create_api_key");
        });
        setBuiltin(env, "db_revoke_api_key", (...args) => {
            const userId = (0, convert_1.asInt)(args[0]);
            const keyId = (0, convert_1.asInt)(args[1]);
            if (isPostgres(db) && userId !== null && keyId !== null) {
                db.revokeApiKey(userId, keyId);
            }
            return values_1.TRUE_OBJ;
        });
        setBuiltin(env, "db_create_trusted", () => stubErr("db_create_trusted"));
        setBuiltin(env, "db_delete_trusted", () => values_1.TRUE_OBJ);
        setBuiltin(env, "db_match_trusted", () => values_1.NULL_OBJ);
        setBuiltin(env, "db_match_trusted_claims", () => values_1.NULL_OBJ);
        setBuiltin(env, "db_explain_trusted_mismatch", () => new values_1.StringObj("no match"));
        setBuiltin(env, "db_mint_publish_token", () => (0, convert_1.fromJs)({ token: "nxs_demo_" + crypto.randomBytes(8).toString("hex"), expires_in: 900 }));
        setBuiltin(env, "db_publish", () => stubErr("db_publish"));
        setBuiltin(env, "db_yank_version", () => stubErr("db_yank_version"));
        setBuiltin(env, "db_unyank_version", () => stubErr("db_unyank_version"));
        setBuiltin(env, "db_deprecate_version", () => stubErr("db_deprecate_version"));
        setBuiltin(env, "db_unpublish_version", () => stubErr("db_unpublish_version"));
        setBuiltin(env, "db_audit_log", () => values_1.TRUE_OBJ);
        setBuiltin(env, "db_create_abuse_report", (...args) => {
            const data = args[0] instanceof values_1.HashObj ? (0, convert_1.toJs)(args[0]) : {};
            if (isPostgres(db)) {
                return (0, convert_1.fromJs)(db.createAbuseReport(data));
            }
            const row = { ID: db.reports.length + 1, ...data, Status: "open" };
            db.reports.push(row);
            return (0, convert_1.fromJs)(row);
        });
        setBuiltin(env, "db_create_auth_token", (...args) => {
            const userId = (0, convert_1.asInt)(args[0]);
            if (isPostgres(db) && userId !== null) {
                return new values_1.StringObj(db.createAuthToken(userId, (0, convert_1.asString)(args[1]) || "verify", (0, convert_1.asInt)(args[2]) ?? 3600));
            }
            return new values_1.StringObj("tok_" + crypto.randomBytes(16).toString("hex"));
        });
        setBuiltin(env, "db_consume_auth_token", (...args) => {
            if (isPostgres(db)) {
                const u = db.consumeAuthToken((0, convert_1.asString)(args[0]) || "", (0, convert_1.asString)(args[1]) || "verify");
                return u ? (0, convert_1.fromJs)(u) : values_1.NULL_OBJ;
            }
            return values_1.NULL_OBJ;
        });
        setBuiltin(env, "db_peek_auth_token", (...args) => {
            if (isPostgres(db)) {
                const u = db.peekAuthToken((0, convert_1.asString)(args[0]) || "", (0, convert_1.asString)(args[1]) || "verify");
                return u ? (0, convert_1.fromJs)(u) : values_1.NULL_OBJ;
            }
            return values_1.NULL_OBJ;
        });
        setBuiltin(env, "db_mark_email_verified", (...args) => {
            const userId = (0, convert_1.asInt)(args[0]);
            if (isPostgres(db) && userId !== null) {
                db.markEmailVerified(userId);
            }
            return values_1.TRUE_OBJ;
        });
        setBuiltin(env, "db_set_password", (...args) => {
            const userId = (0, convert_1.asInt)(args[0]);
            if (isPostgres(db) && userId !== null) {
                db.setPassword(userId, (0, convert_1.asString)(args[1]) || "");
            }
            return values_1.TRUE_OBJ;
        });
        setBuiltin(env, "db_totp_begin", () => stubErr("db_totp_begin"));
        setBuiltin(env, "db_totp_confirm", () => stubErr("db_totp_confirm"));
        setBuiltin(env, "db_totp_disable", () => stubErr("db_totp_disable"));
        setBuiltin(env, "db_totp_challenge_create", () => stubErr("db_totp_challenge_create"));
        setBuiltin(env, "db_totp_challenge_consume", () => stubErr("db_totp_challenge_consume"));
        setBuiltin(env, "db_mark_trusted_verified", () => values_1.TRUE_OBJ);
        setBuiltin(env, "db_record_trusted_failure", () => values_1.TRUE_OBJ);
        setBuiltin(env, "db_set_version_provenance", () => values_1.TRUE_OBJ);
        setBuiltin(env, "db_get_org", (...args) => {
            if (isPostgres(db)) {
                const org = db.getOrg((0, convert_1.asString)(args[0]) || "");
                return org ? (0, convert_1.fromJs)(org) : values_1.NULL_OBJ;
            }
            return values_1.NULL_OBJ;
        });
        setBuiltin(env, "db_create_org", (...args) => {
            const userId = (0, convert_1.asInt)(args[3]);
            if (isPostgres(db) && userId !== null) {
                const org = db.createOrg((0, convert_1.asString)(args[0]) || "", (0, convert_1.asString)(args[1]) || "", (0, convert_1.asString)(args[2]) || "", userId);
                return org ? (0, convert_1.fromJs)(org) : stubErr("db_create_org");
            }
            return stubErr("db_create_org");
        });
        setBuiltin(env, "db_list_org_members", () => (0, convert_1.fromJs)([]));
        setBuiltin(env, "db_list_org_packages", () => (0, convert_1.fromJs)([]));
        setBuiltin(env, "db_list_org_activity", () => (0, convert_1.fromJs)([]));
        setBuiltin(env, "db_list_teams", () => (0, convert_1.fromJs)([]));
        setBuiltin(env, "db_org_member_role", () => new values_1.StringObj(""));
        setBuiltin(env, "db_add_org_member", () => values_1.TRUE_OBJ);
        setBuiltin(env, "db_remove_org_member", () => values_1.TRUE_OBJ);
        setBuiltin(env, "db_create_team", () => stubErr("db_create_team"));
        setBuiltin(env, "db_add_team_member", () => values_1.TRUE_OBJ);
        setBuiltin(env, "db_list_team_members", () => (0, convert_1.fromJs)([]));
        setBuiltin(env, "db_invite_package_owner", () => stubErr("db_invite_package_owner"));
        setBuiltin(env, "db_add_org_package_owner", () => values_1.TRUE_OBJ);
        setBuiltin(env, "db_remove_package_owner", () => values_1.TRUE_OBJ);
        setBuiltin(env, "db_accept_owner_invite", () => values_1.TRUE_OBJ);
        setBuiltin(env, "db_create_ownership_transfer", () => stubErr("db_create_ownership_transfer"));
        setBuiltin(env, "db_accept_ownership_transfer", () => values_1.TRUE_OBJ);
    }
    async function readBody(req, limit) {
        return new Promise((resolve, reject) => {
            const chunks = [];
            let size = 0;
            req.on("data", (c) => {
                size += c.length;
                if (size > limit) {
                    reject(new Error("body too large"));
                    req.destroy();
                    return;
                }
                chunks.push(c);
            });
            req.on("end", () => resolve(Buffer.concat(chunks)));
            req.on("error", reject);
        });
    }
    async function handleRequest(req, res) {
        const hostHeader = req.headers.host || "localhost";
        const url = new URL(req.url || "/", `http://${hostHeader}`);
        const pathname = decodeURIComponent(url.pathname);
        // Static files
        if (pathname === "/static/js/nxd.js") {
            res.statusCode = 200;
            res.setHeader("Content-Type", "application/javascript; charset=utf-8");
            res.setHeader("Cache-Control", "public, max-age=3600");
            res.end((0, design_1.nxdKitScript)());
            return;
        }
        if (pathname.startsWith("/static/") && config.webDir) {
            const rel = pathname.slice("/static/".length);
            const filePath = path.normalize(path.join(config.webDir, "static", rel));
            const root = path.normalize(path.join(config.webDir, "static"));
            if (!filePath.startsWith(root) || !fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
                res.statusCode = 404;
                res.end("not found");
                return;
            }
            const ext = path.extname(filePath).toLowerCase();
            const types = {
                ".css": "text/css; charset=utf-8",
                ".js": "application/javascript; charset=utf-8",
                ".svg": "image/svg+xml",
                ".png": "image/png",
                ".jpg": "image/jpeg",
                ".jpeg": "image/jpeg",
                ".ico": "image/x-icon",
                ".woff2": "font/woff2",
            };
            res.statusCode = 200;
            res.setHeader("Content-Type", types[ext] || "application/octet-stream");
            fs.createReadStream(filePath).pipe(res);
            return;
        }
        if (pathname === "/metrics") {
            if (isPostgres(db)) {
                db.refreshCaches();
            }
            const pkgCount = db.packages.length;
            res.statusCode = 200;
            res.setHeader("Content-Type", "text/plain; version=0.0.4");
            res.end(`# TYPE nex_up gauge\nnex_up 1\n# TYPE nex_packages gauge\nnex_packages ${pkgCount}\n# TYPE nex_db_mode gauge\nnex_db_postgres ${isPostgres(db) ? 1 : 0}\n`);
            return;
        }
        const method = (req.method || "GET").toUpperCase();
        const match = router.match(method, pathname);
        const { user, via } = resolveUser(req);
        const requestId = crypto.randomBytes(8).toString("hex");
        if (match?.requireAuth && !user) {
            if (pathname.startsWith("/api/")) {
                res.statusCode = 401;
                res.setHeader("Content-Type", "application/json");
                res.end(JSON.stringify({ error: "authentication required" }));
                return;
            }
            res.statusCode = 303;
            res.setHeader("Location", `/login?next=${encodeURIComponent(pathname)}`);
            res.end();
            return;
        }
        const headers = {};
        for (const [k, v] of Object.entries(req.headers)) {
            if (typeof v === "string") {
                headers[k.toLowerCase()] = v;
            }
            else if (Array.isArray(v) && v[0]) {
                headers[k.toLowerCase()] = v[0];
            }
        }
        const incoming = {
            method,
            path: pathname,
            query: parseQuery(url),
            headers,
            params: match?.params ?? {},
            cookies: parseCookies(req.headers.cookie),
            form: {},
            body: "",
            requestId,
            user,
            authVia: via,
        };
        const ct = (req.headers["content-type"] || "").toLowerCase();
        if (method === "POST" || method === "PUT" || method === "PATCH") {
            try {
                const raw = await readBody(req, config.maxUploadBytes);
                if (ct.includes("multipart/form-data")) {
                    const m = /boundary=(?:"([^"]+)"|([^;]+))/i.exec(ct);
                    const boundary = (m?.[1] || m?.[2] || "").trim();
                    if (boundary) {
                        const parsed = parseMultipart(raw, boundary);
                        forms.set(requestId, parsed);
                        incoming.form = parsed.fields;
                    }
                }
                else if (ct.includes("application/x-www-form-urlencoded")) {
                    incoming.form = parseFormUrlEncoded(raw.toString("utf8"));
                }
                else {
                    incoming.body = raw.toString("utf8");
                }
            }
            catch (e) {
                res.statusCode = 413;
                res.end(e instanceof Error ? e.message : "body error");
                return;
            }
        }
        const handler = match?.handler ??
            router.notFoundHandler ??
            (() => {
                const out = new values_1.HashObj();
                out.setString("status", new values_1.IntegerObj(404));
                out.setString("json", (0, convert_1.fromJs)({ error: "not found" }));
                return out;
            });
        try {
            const result = handler(incoming);
            const value = result instanceof Promise ? await result : result;
            const nex = value && typeof value === "object" && "type" in value
                ? value
                : (0, convert_1.fromJs)(value);
            if (nex instanceof values_1.ErrorObj) {
                // eslint-disable-next-line no-console
                console.error("nex handler error", pathname, nex.message);
                const accept = req.headers.accept || "";
                if (accept.includes("text/html")) {
                    writeResponse(res, req, (() => {
                        const out = new values_1.HashObj();
                        out.setString("status", new values_1.IntegerObj(500));
                        out.setString("html", new values_1.StringObj("error.html"));
                        out.setString("data", (0, convert_1.fromJs)({ Title: "Error", Status: 500, Message: "Internal error" }));
                        return out;
                    })(), user);
                    return;
                }
                res.statusCode = 500;
                res.setHeader("Content-Type", "application/json");
                res.end(JSON.stringify({ error: "internal error", details: nex.message }));
                return;
            }
            writeResponse(res, req, nex, user);
        }
        catch (err) {
            // eslint-disable-next-line no-console
            console.error("handler exception", err);
            res.statusCode = 500;
            res.end("internal error");
        }
        finally {
            forms.delete(requestId);
        }
    }
    function listenAndServe() {
        const { host: listenHost, port } = parseListenAddr(config.listenAddr);
        return new Promise((resolve, reject) => {
            server = http.createServer((req, res) => {
                void handleRequest(req, res);
            });
            server.on("error", reject);
            server.listen(port, listenHost, () => {
                // eslint-disable-next-line no-console
                console.log(JSON.stringify({
                    msg: "nex listening",
                    addr: config.listenAddr,
                    host: listenHost,
                    port,
                    storage: config.storageDir,
                    base_url: config.baseUrl,
                    app: config.appDir,
                    web: config.webDir,
                    routes: router.routeCount,
                    db: dbMode,
                }));
                // Keep promise pending until close — CLI awaits this
                server.on("close", () => resolve());
            });
        });
    }
    return host;
}
//# sourceMappingURL=index.js.map