"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.BUILTIN_MODULES = void 0;
exports.addModulesCommand = addModulesCommand;
const child_process_1 = require("child_process");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const config_1 = require("../config");
const readline = __importStar(require("readline"));
exports.BUILTIN_MODULES = [
    "@tyvm/knowhow-module-script",
    "@tyvm/knowhow-module-terminal",
];
function getKnowhowDir(isGlobal) {
    if (isGlobal) {
        return path.join(os.homedir(), ".knowhow");
    }
    return path.join(process.cwd(), ".knowhow");
}
function ensureKnowhowPackageJson(knowhowDir) {
    const pkgPath = path.join(knowhowDir, "package.json");
    if (!fs.existsSync(pkgPath)) {
        fs.mkdirSync(knowhowDir, { recursive: true });
        fs.writeFileSync(pkgPath, JSON.stringify({ name: "knowhow-modules", private: true, version: "1.0.0" }, null, 2));
    }
}
function npmInstallToKnowhow(mod, knowhowDir) {
    (0, child_process_1.execSync)(`npm install --prefix "${knowhowDir}" ${mod}`, {
        stdio: "inherit",
        encoding: "utf-8",
    });
}
function isModuleInstalled(mod, knowhowDir) {
    try {
        require.resolve(mod, {
            paths: [path.join(knowhowDir, "node_modules"), knowhowDir],
        });
        return true;
    }
    catch {
        return false;
    }
}
async function fetchNpmRegistryInfo(mod) {
    try {
        const output = (0, child_process_1.execSync)(`npm view ${mod} version time --json`, {
            stdio: ["ignore", "pipe", "pipe"],
            encoding: "utf-8",
        });
        const parsed = JSON.parse(output.trim());
        let latestVersion;
        let timestamps = {};
        if (Array.isArray(parsed)) {
            latestVersion = parsed[0];
            timestamps = parsed[1] || {};
        }
        else if (parsed && typeof parsed === "object") {
            latestVersion = parsed["version"] || "";
            timestamps = parsed["time"] || {};
        }
        else {
            latestVersion = String(parsed);
        }
        const publishedAt = timestamps[latestVersion] || timestamps["modified"] || "";
        return { latestVersion, publishedAt };
    }
    catch {
        return null;
    }
}
function nodeSatisfiesRange(nodeVersion, range) {
    if (!range || range === "*" || range === "")
        return true;
    const vMatch = nodeVersion.replace(/^v/, "").match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
    if (!vMatch)
        return true;
    const vMajor = parseInt(vMatch[1], 10);
    const vMinor = parseInt(vMatch[2] ?? "0", 10);
    const vPatch = parseInt(vMatch[3] ?? "0", 10);
    const vNum = vMajor * 1_000_000 + vMinor * 1_000 + vPatch;
    function parseVersion(s) {
        const m = s.trim().match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
        if (!m)
            return 0;
        return parseInt(m[1], 10) * 1_000_000 + parseInt(m[2] ?? "0", 10) * 1_000 + parseInt(m[3] ?? "0", 10);
    }
    if (range.includes("||")) {
        return range.split("||").some((r) => nodeSatisfiesRange(nodeVersion, r.trim()));
    }
    const parts = range.trim().split(/\s+/);
    for (const part of parts) {
        const gteMatch = part.match(/^>=(.+)/);
        const gtMatch = part.match(/^>(?!=)(.+)/);
        const lteMatch = part.match(/^<=(.+)/);
        const ltMatch = part.match(/^<(?!=)(.+)/);
        const caretMatch = part.match(/^\^(\d+)/);
        const tildeMatch = part.match(/^~(\d+)(?:\.(\d+))?/);
        const exactMatch = part.match(/^(\d+(?:\.\d+)*)/);
        if (gteMatch) {
            if (vNum < parseVersion(gteMatch[1]))
                return false;
        }
        else if (gtMatch) {
            if (vNum <= parseVersion(gtMatch[1]))
                return false;
        }
        else if (lteMatch) {
            if (vNum > parseVersion(lteMatch[1]))
                return false;
        }
        else if (ltMatch) {
            if (vNum >= parseVersion(ltMatch[1]))
                return false;
        }
        else if (caretMatch) {
            const base = parseVersion(caretMatch[1]);
            const baseMajor = parseInt(caretMatch[1], 10);
            if (vNum < base || vMajor !== baseMajor)
                return false;
        }
        else if (tildeMatch) {
            const baseMajor = parseInt(tildeMatch[1], 10);
            const baseMinor = parseInt(tildeMatch[2] ?? "0", 10);
            const base = parseVersion(`${baseMajor}.${baseMinor}`);
            const nextMinor = parseVersion(`${baseMajor}.${baseMinor + 1}`);
            if (vNum < base || vNum >= nextMinor)
                return false;
        }
        else if (exactMatch && !part.startsWith("v")) {
            const xMatch = part.match(/^(\d+)(?:\.x)?$/);
            if (xMatch) {
                if (vMajor !== parseInt(xMatch[1], 10))
                    return false;
            }
        }
    }
    return true;
}
async function fetchLatestCompatibleVersion(mod) {
    const currentNode = process.version;
    try {
        const encodedMod = mod.replace(/^@/, "").replace("/", "%2F");
        const registryUrl = mod.startsWith("@")
            ? `https://registry.npmjs.org/@${encodedMod}`
            : `https://registry.npmjs.org/${mod}`;
        const response = await fetch(registryUrl);
        if (!response.ok)
            throw new Error(`Registry returned ${response.status}`);
        const pkgData = await response.json();
        const versionsMap = pkgData.versions ?? {};
        const allVersions = Object.keys(versionsMap);
        const enginesByVersion = {};
        for (const [v, meta] of Object.entries(versionsMap)) {
            enginesByVersion[v] = meta?.engines?.node ?? "";
        }
        if (allVersions.length === 0)
            return `${mod}@latest`;
        const sorted = [...allVersions].sort((a, b) => {
            const pa = a.split(".").map(Number);
            const pb = b.split(".").map(Number);
            for (let i = 0; i < 3; i++) {
                if ((pa[i] ?? 0) !== (pb[i] ?? 0))
                    return (pb[i] ?? 0) - (pa[i] ?? 0);
            }
            return 0;
        });
        for (const v of sorted) {
            const engineRange = enginesByVersion[v] ?? "";
            if (nodeSatisfiesRange(currentNode, engineRange)) {
                return `${mod}@${v}`;
            }
        }
        console.warn(`⚠️  No version of ${mod} found compatible with Node ${currentNode}. Installing latest anyway.`);
        return `${mod}@latest`;
    }
    catch {
        return `${mod}@latest`;
    }
}
function getInstalledVersion(mod, knowhowDir) {
    try {
        const pkgJsonPath = path.join(knowhowDir, "node_modules", mod, "package.json");
        const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
        return pkgJson.version ?? null;
    }
    catch {
        return null;
    }
}
function formatRelativeTime(isoDate) {
    if (!isoDate)
        return "unknown";
    const then = new Date(isoDate).getTime();
    if (isNaN(then))
        return "unknown";
    const diffMs = Date.now() - then;
    const diffMins = Math.floor(diffMs / 60_000);
    if (diffMins < 2)
        return "just now";
    if (diffMins < 60)
        return `${diffMins} minutes ago`;
    const diffHours = Math.floor(diffMins / 60);
    if (diffHours < 24)
        return `${diffHours} hour${diffHours !== 1 ? "s" : ""} ago`;
    const diffDays = Math.floor(diffHours / 24);
    if (diffDays < 30)
        return `${diffDays} day${diffDays !== 1 ? "s" : ""} ago`;
    const diffMonths = Math.floor(diffDays / 30);
    if (diffMonths < 12)
        return `${diffMonths} month${diffMonths !== 1 ? "s" : ""} ago`;
    const diffYears = Math.floor(diffMonths / 12);
    return `${diffYears} year${diffYears !== 1 ? "s" : ""} ago`;
}
function promptConfirm(question) {
    return new Promise((resolve) => {
        const rl = readline.createInterface({
            input: process.stdin,
            output: process.stdout,
        });
        rl.question(`${question} (y/N) `, (answer) => {
            rl.close();
            resolve(answer.trim().toLowerCase() === "y" ||
                answer.trim().toLowerCase() === "yes");
        });
    });
}
function addModulesCommand(program) {
    const modulesCmd = program
        .command("modules")
        .description("Manage knowhow modules (install, add to config, list)");
    modulesCmd
        .command("setup")
        .description("Add default built-in modules to your config and install them into .knowhow/node_modules")
        .option("--global", "Use the global config (~/.knowhow/knowhow.json)")
        .action(async (opts) => {
        try {
            const isGlobal = opts.global ?? false;
            const cfg = isGlobal ? await (0, config_1.getGlobalConfig)() : await (0, config_1.getConfig)();
            const configLabel = isGlobal
                ? "~/.knowhow/knowhow.json"
                : ".knowhow/knowhow.json";
            if (!cfg.modules)
                cfg.modules = [];
            const knowhowDir = getKnowhowDir(isGlobal);
            ensureKnowhowPackageJson(knowhowDir);
            let anyChanges = false;
            for (const mod of exports.BUILTIN_MODULES) {
                if (!mod.startsWith(".") && !mod.startsWith("/")) {
                    if (!isModuleInstalled(mod, knowhowDir)) {
                        const installTarget = await fetchLatestCompatibleVersion(mod);
                        console.log(`📦 Installing ${installTarget}...`);
                        npmInstallToKnowhow(installTarget, knowhowDir);
                        console.log(`✅ Installed ${mod}`);
                        anyChanges = true;
                    }
                }
                if (!cfg.modules.includes(mod)) {
                    cfg.modules.push(mod);
                    console.log(`✅ Added ${mod} to ${configLabel}`);
                    anyChanges = true;
                }
            }
            if (!anyChanges) {
                console.log(`✅ All default modules are already in ${configLabel} and installed. Nothing to do.`);
            }
            else {
                await (isGlobal ? (0, config_1.updateGlobalConfig)(cfg) : (0, config_1.updateConfig)(cfg));
                console.log(`\n🎉 Setup complete! Modules ready in ${knowhowDir}/node_modules.`);
            }
        }
        catch (error) {
            console.error("Error during modules setup:", error.message ?? error);
            process.exit(1);
        }
    });
    modulesCmd
        .command("install [module]")
        .description("Install a module into .knowhow/node_modules and add it to your config. " +
        "If no module name is given, installs all modules already in the config.")
        .option("--global", "Use the global config (~/.knowhow/knowhow.json)")
        .option("--latest", "Force install the latest version (bypasses package-lock)")
        .action(async (moduleName, opts) => {
        try {
            const isGlobal = opts.global ?? false;
            const cfg = isGlobal ? await (0, config_1.getGlobalConfig)() : await (0, config_1.getConfig)();
            const configLabel = isGlobal
                ? "~/.knowhow/knowhow.json"
                : ".knowhow/knowhow.json";
            if (!cfg.modules)
                cfg.modules = [];
            const knowhowDir = getKnowhowDir(isGlobal);
            ensureKnowhowPackageJson(knowhowDir);
            if (!moduleName) {
                const installable = cfg.modules.filter((m) => !m.startsWith(".") && !m.startsWith("/"));
                if (installable.length === 0) {
                    console.log(`ℹ No installable modules found in ${configLabel}.`);
                    return;
                }
                console.log(`📦 Installing ${installable.length} module(s) from ${configLabel} into ${knowhowDir}/node_modules...`);
                for (const mod of installable) {
                    console.log(`  📦 Installing ${mod}...`);
                    const installTarget = opts.latest ? await fetchLatestCompatibleVersion(mod) : mod;
                    npmInstallToKnowhow(installTarget, knowhowDir);
                    console.log(`  ✅ Installed ${mod}`);
                }
                console.log(`\n🎉 All modules installed!`);
                return;
            }
            const installTarget = opts.latest ? await fetchLatestCompatibleVersion(moduleName) : moduleName;
            console.log(`📦 Installing ${installTarget} into ${knowhowDir}/node_modules...`);
            npmInstallToKnowhow(installTarget, knowhowDir);
            console.log(`✅ Installed ${moduleName}`);
            if (!cfg.modules.includes(moduleName)) {
                cfg.modules.push(moduleName);
                if (isGlobal) {
                    await (0, config_1.updateGlobalConfig)(cfg);
                }
                else {
                    await (0, config_1.updateConfig)(cfg);
                }
                console.log(`✅ Added ${moduleName} to ${configLabel}`);
            }
            else {
                console.log(`ℹ ${moduleName} is already in ${configLabel}`);
            }
        }
        catch (error) {
            console.error("Error during module install:", error.message ?? error);
            process.exit(1);
        }
    });
    modulesCmd
        .command("list")
        .description("List all modules in your config")
        .option("--global", "Show global config modules only")
        .action(async (opts) => {
        try {
            const isGlobal = opts.global ?? false;
            const globalCfg = await (0, config_1.getGlobalConfig)();
            const localCfg = isGlobal ? null : await (0, config_1.getConfig)();
            const globalModules = globalCfg.modules || [];
            const localModules = localCfg?.modules || [];
            if (isGlobal) {
                console.log(`\n🌐 Global modules (~/.knowhow/knowhow.json):`);
                if (globalModules.length === 0) {
                    console.log("  (none)");
                }
                else {
                    globalModules.forEach((m, i) => console.log(`  ${i + 1}. ${m}`));
                }
            }
            else {
                console.log(`\n🌐 Global modules (~/.knowhow/knowhow.json):`);
                if (globalModules.length === 0) {
                    console.log("  (none)");
                }
                else {
                    globalModules.forEach((m, i) => console.log(`  ${i + 1}. ${m}`));
                }
                console.log(`\n📁 Local modules (.knowhow/knowhow.json):`);
                if (localModules.length === 0) {
                    console.log("  (none)");
                }
                else {
                    localModules.forEach((m, i) => console.log(`  ${i + 1}. ${m}`));
                }
            }
        }
        catch (error) {
            console.error("Error listing modules:", error.message ?? error);
            process.exit(1);
        }
    });
    modulesCmd
        .command("update")
        .description("Check for updates to all modules in your config and update them. " +
        "Shows installed vs latest version with publish date before updating.")
        .option("--global", "Use the global config (~/.knowhow/knowhow.json)")
        .option("-y, --yes", "Skip confirmation prompt and update all outdated modules automatically")
        .action(async (opts) => {
        try {
            const isGlobal = opts.global ?? false;
            const skipConfirm = opts.yes ?? false;
            const cfg = isGlobal ? await (0, config_1.getGlobalConfig)() : await (0, config_1.getConfig)();
            const configLabel = isGlobal
                ? "~/.knowhow/knowhow.json"
                : ".knowhow/knowhow.json";
            if (!cfg.modules || cfg.modules.length === 0) {
                console.log(`ℹ No modules found in ${configLabel}.`);
                return;
            }
            const knowhowDir = getKnowhowDir(isGlobal);
            ensureKnowhowPackageJson(knowhowDir);
            const installable = cfg.modules.filter((m) => !m.startsWith(".") && !m.startsWith("/"));
            if (installable.length === 0) {
                console.log(`ℹ No npm modules found in ${configLabel}.`);
                return;
            }
            console.log(`🔍 Checking for updates to ${installable.length} module(s)...\n`);
            const updates = [];
            for (const mod of installable) {
                const registryInfo = await fetchNpmRegistryInfo(mod);
                if (!registryInfo) {
                    console.log(`  ⚠️  ${mod}: could not fetch registry info (skipping)`);
                    continue;
                }
                const installed = getInstalledVersion(mod, knowhowDir);
                const { latestVersion, publishedAt } = registryInfo;
                const compatibleInstallTarget = await fetchLatestCompatibleVersion(mod);
                const compatibleVersion = compatibleInstallTarget.replace(/^[^@]+@/, "");
                const needsUpdate = installed !== compatibleVersion;
                const timeAgo = formatRelativeTime(publishedAt);
                if (needsUpdate) {
                    const installedStr = installed ?? "(not installed)";
                    const versionLabel = compatibleVersion !== latestVersion
                        ? `${compatibleVersion} (latest compatible with Node ${process.version}; absolute latest: ${latestVersion})`
                        : compatibleVersion;
                    console.log(`  📦 ${mod}`);
                    console.log(`     installed: ${installedStr}  →  latest: ${versionLabel} (published ${timeAgo})`);
                }
                else {
                    console.log(`  ✅ ${mod}  v${compatibleVersion}  (up to date, published ${timeAgo})`);
                }
                updates.push({ mod, installed, latest: compatibleVersion, compatibleVersion: compatibleInstallTarget, publishedAt, needsUpdate });
            }
            const toUpdate = updates.filter((u) => u.needsUpdate);
            if (toUpdate.length === 0) {
                console.log(`\n✅ All modules are up to date!`);
                return;
            }
            console.log(`\n${toUpdate.length} module(s) can be updated.`);
            if (!skipConfirm) {
                const confirmed = await promptConfirm(`Update ${toUpdate.length} module(s) now?`);
                if (!confirmed) {
                    console.log("Cancelled.");
                    return;
                }
            }
            console.log("");
            for (const { mod, compatibleVersion } of toUpdate) {
                console.log(`  📦 Updating ${compatibleVersion}...`);
                npmInstallToKnowhow(compatibleVersion, knowhowDir);
                console.log(`  ✅ Updated ${mod}`);
            }
            console.log(`\n🎉 Update complete! ${toUpdate.length} module(s) updated.`);
        }
        catch (error) {
            console.error("Error during modules update:", error.message ?? error);
            process.exit(1);
        }
    });
}
//# sourceMappingURL=modules.js.map