"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.findNssDatabases = findNssDatabases;
exports.bundledCertutilPath = bundledCertutilPath;
exports.importRootCaToNssDatabases = importRootCaToNssDatabases;
exports.rootCaPath = rootCaPath;
const fs = __importStar(require("fs"));
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const child_process_1 = require("child_process");
function unique(values) {
    return Array.from(new Set(values));
}
function existingDbAt(dir) {
    const cert9 = path.join(dir, 'cert9.db');
    if (fs.existsSync(cert9)) {
        return { dir, version: 'cert9' };
    }
    const cert8 = path.join(dir, 'cert8.db');
    if (fs.existsSync(cert8)) {
        return { dir, version: 'cert8' };
    }
    return undefined;
}
function defaultNssDirs(homeDir) {
    const xdgConfigHome = process.env.XDG_CONFIG_HOME || path.join(homeDir, '.config');
    const xdgDataHome = process.env.XDG_DATA_HOME || path.join(homeDir, '.local', 'share');
    const firefoxProfileRoots = [
        path.join(homeDir, '.mozilla', 'firefox'),
        path.join(xdgConfigHome, 'mozilla', 'firefox'),
        path.join(homeDir, 'snap', 'firefox', 'common', '.mozilla', 'firefox')
    ];
    const firefoxProfiles = firefoxProfileRoots.flatMap((root) => {
        if (!fs.existsSync(root)) {
            return [];
        }
        return fs.readdirSync(root, { withFileTypes: true })
            .filter((entry) => entry.isDirectory())
            .map((entry) => path.join(root, entry.name));
    });
    return [
        path.join(homeDir, '.pki', 'nssdb'),
        path.join(xdgDataHome, 'pki', 'nssdb'),
        path.join(homeDir, 'snap', 'chromium', 'current', '.pki', 'nssdb'),
        '/etc/pki/nssdb',
        ...firefoxProfiles
    ];
}
function searchHomeForNssDbs(homeDir) {
    const results = [];
    const visited = new Set();
    const queue = [homeDir];
    while (queue.length > 0) {
        const current = queue.shift();
        if (visited.has(current)) {
            continue;
        }
        visited.add(current);
        let entries;
        try {
            entries = fs.readdirSync(current, { withFileTypes: true });
        }
        catch {
            continue;
        }
        const db = existingDbAt(current);
        if (db) {
            results.push(db);
        }
        for (const entry of entries) {
            if (!entry.isDirectory() || entry.isSymbolicLink()) {
                continue;
            }
            queue.push(path.join(current, entry.name));
        }
    }
    return results;
}
function findNssDatabases(homeDir = os.homedir()) {
    const defaults = defaultNssDirs(homeDir)
        .map((dir) => existingDbAt(dir))
        .filter((db) => Boolean(db));
    const found = defaults.length > 0 ? defaults : searchHomeForNssDbs(homeDir);
    const seen = new Set();
    return found.filter((db) => {
        const key = `${db.version}:${path.resolve(db.dir)}`;
        if (seen.has(key)) {
            return false;
        }
        seen.add(key);
        return true;
    });
}
function bundledCertutilPath() {
    const platformName = process.platform === 'darwin' ? 'darwin' : process.platform === 'linux' ? 'linux' : undefined;
    const archName = process.arch === 'x64' ? 'x64' : process.arch === 'arm64' ? 'arm64' : undefined;
    if (!platformName || !archName) {
        throw new Error(`Bundled NSS certutil is not available for ${process.platform}/${process.arch}.`);
    }
    const certutil = path.resolve(__dirname, '..', '..', 'vendor', 'nss', `${platformName}-${archName}`, 'certutil');
    if (!fs.existsSync(certutil)) {
        throw new Error(`Bundled NSS certutil was not found at ${certutil}.`);
    }
    return certutil;
}
function certutilSpawnOptions(certutilPath) {
    const libDir = path.join(path.dirname(certutilPath), 'lib');
    const env = { ...process.env };
    if (process.platform === 'linux') {
        env.LD_LIBRARY_PATH = [libDir, env.LD_LIBRARY_PATH].filter(Boolean).join(path.delimiter);
    }
    if (process.platform === 'darwin') {
        env.DYLD_LIBRARY_PATH = [libDir, env.DYLD_LIBRARY_PATH].filter(Boolean).join(path.delimiter);
        env.DYLD_FALLBACK_LIBRARY_PATH = [libDir, env.DYLD_FALLBACK_LIBRARY_PATH].filter(Boolean).join(path.delimiter);
    }
    try {
        fs.chmodSync(certutilPath, 0o755);
    }
    catch {
        // If chmod is unavailable, let spawn surface the platform-specific error.
    }
    return { env, stdio: 'pipe' };
}
function certutilDatabaseArg(db) {
    return db.version === 'cert9' ? `sql:${db.dir}` : db.dir;
}
function importRootCaToNssDatabases(rootCaPath, options = {}) {
    if (!fs.existsSync(rootCaPath)) {
        throw new Error(`Root CA file was not found: ${rootCaPath}`);
    }
    const certutilPath = options.certutilPath || bundledCertutilPath();
    const runner = options.runner || child_process_1.spawnSync;
    const databases = findNssDatabases(options.homeDir);
    if (databases.length === 0) {
        throw new Error('No Firefox or Chrome/Chromium NSS security databases were found.');
    }
    const imported = [];
    const skipped = [];
    for (const db of databases) {
        const args = [
            '-A',
            '-d',
            certutilDatabaseArg(db),
            '-t',
            'C,,',
            '-n',
            'Runestone Local CA',
            '-i',
            rootCaPath
        ];
        const result = runner(certutilPath, args, certutilSpawnOptions(certutilPath));
        if (result.status === 0) {
            imported.push(db);
            continue;
        }
        const stderr = result.stderr?.toString() ?? '';
        if (/SEC_ERROR_ADDING_CERT|already exists|exists/i.test(stderr)) {
            skipped.push(db);
            continue;
        }
        throw new Error(`Failed to import root CA into ${db.dir}: ${stderr || result.error?.message || 'unknown error'}`);
    }
    return { certutilPath, imported, skipped };
}
function rootCaPath(projectDir) {
    const certsDir = path.join(projectDir, 'certs');
    const candidates = unique([
        path.join(certsDir, 'rootCA.pem'),
        path.join(certsDir, 'rootCA.crt')
    ]);
    const existing = candidates.find((candidate) => fs.existsSync(candidate));
    if (!existing) {
        throw new Error(`Root CA file was not found in ${certsDir}. Run setup or create a certificate first.`);
    }
    return existing;
}
//# sourceMappingURL=nss-certutil.js.map