"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.selectToolNames = selectToolNames;
exports.runToolsCommand = runToolsCommand;
/**
 * CLI handler for `vyuh-dxkit tools` subcommand.
 *
 * Modes:
 * - `vyuh-dxkit tools`             → list status for required tools
 * - `vyuh-dxkit tools install`     → interactive install of missing tools
 *                                    for the current-project stack
 * - `vyuh-dxkit tools install <name>` → install one named tool (any
 *                                    stack); useful for cross-stack dev
 *                                    work where you need a tool that
 *                                    your current project doesn't
 *                                    declare (e.g. installing
 *                                    `spotbugs` on a Node-only repo)
 * - `vyuh-dxkit tools install --all` → enumerate every known tool in
 *                                    TOOL_DEFS and install missing
 *                                    ones; useful for setting up a
 *                                    cross-stack dev environment
 * - `vyuh-dxkit tools install --yes` → install all missing, no prompts
 */
const readline = __importStar(require("readline/promises"));
const process_1 = require("process");
const child_process_1 = require("child_process");
const detect_1 = require("./detect");
const logger = __importStar(require("./logger"));
const tool_registry_1 = require("./analyzers/tools/tool-registry");
/**
 * Decide which tool names to consider for install given the options.
 * Pure function — does not touch the filesystem; testable in isolation.
 *
 * - toolName set: just that one (returns [] if name is unknown — caller
 *   surfaces an error)
 * - all: every key in TOOL_DEFS, sorted for stable output
 * - default: tools required for the current-project stack
 */
function selectToolNames(languages, options = {}) {
    if (options.toolName) {
        return tool_registry_1.TOOL_DEFS[options.toolName] ? [options.toolName] : [];
    }
    if (options.all) {
        return Object.keys(tool_registry_1.TOOL_DEFS).sort();
    }
    return (0, tool_registry_1.buildRequiredTools)(languages).map((r) => r.name);
}
const LAYER_ORDER = {
    universal: 1,
    language: 2,
    optional: 3,
};
const LAYER_LABEL = {
    universal: 'universal',
    language: 'language ',
    optional: 'optional ',
};
function sortByLayer(statuses) {
    return [...statuses].sort((a, b) => {
        const la = LAYER_ORDER[a.requirement.layer] || 99;
        const lb = LAYER_ORDER[b.requirement.layer] || 99;
        if (la !== lb)
            return la - lb;
        return a.name.localeCompare(b.name);
    });
}
function formatStatusLine(s) {
    const name = s.name.padEnd(16);
    const layer = LAYER_LABEL[s.requirement.layer] || s.requirement.layer;
    const forStack = s.requirement.for.padEnd(7);
    if (s.available) {
        const src = s.source === 'path' ? '' : ` (${s.source})`;
        return `  \x1b[32m✓\x1b[0m ${name}  ${layer}  ${forStack}  ${logger.bold('found')}${src}`;
    }
    // N/A tools (applicability gate fired): show a distinct icon + the
    // reason so users see "not for this stack" rather than "missing".
    // These intentionally do NOT count toward the missing tally.
    if (s.source === 'n/a') {
        const reason = s.notApplicableReason ? ` \x1b[2m(${s.notApplicableReason})\x1b[0m` : '';
        return `  \x1b[2m−\x1b[0m ${name}  ${layer}  ${forStack}  \x1b[2mn/a${reason ? '' : ' for this stack'}\x1b[0m${reason}`;
    }
    // Project-local tools (F-UX-3): annotate so users don't confuse a
    // missing dev-dep with a missing system tool.
    const scopeNote = s.requirement.installScope === 'project-local'
        ? ' \x1b[2m(project-local — `npm ci`)\x1b[0m'
        : '';
    return `  \x1b[31m✗\x1b[0m ${name}  ${layer}  ${forStack}  \x1b[2mmissing\x1b[0m${scopeNote}`;
}
/** Show tool status for the repo's detected stack. */
function showStatus(targetPath) {
    const stack = (0, detect_1.detect)(targetPath);
    const langs = Object.entries(stack.languages)
        .filter(([, v]) => v)
        .map(([k]) => k);
    logger.header('vyuh-dxkit tools');
    logger.info(`Stack: ${langs.join(', ') || 'unknown'}`);
    console.log('');
    const statuses = sortByLayer((0, tool_registry_1.checkAllTools)(stack.languages, targetPath));
    console.log(`  ${logger.bold('Tool'.padEnd(16))}  ${logger.bold('layer'.padEnd(10))}  ${logger.bold('for'.padEnd(7))}  ${logger.bold('status')}`);
    console.log(`  ${'─'.repeat(16)}  ${'─'.repeat(10)}  ${'─'.repeat(7)}  ${'─'.repeat(20)}`);
    for (const s of statuses) {
        console.log(formatStatusLine(s));
        if (s.available && s.version) {
            logger.dim(`    version: ${s.version}`);
        }
        if (s.available && s.path && s.source !== 'path') {
            logger.dim(`    path: ${s.path}`);
        }
    }
    // Split the unavailable set: truly missing (actionable) vs n/a
    // (informational — the applicability gate excluded the tool from
    // this stack). Missing-count math must not include n/a entries or
    // customers see false "1/14 missing" alarms on repos that
    // legitimately don't use the tool.
    const missing = statuses.filter((s) => !s.available && s.source !== 'n/a');
    const notApplicable = statuses.filter((s) => s.source === 'n/a');
    const total = statuses.length;
    const applicable = total - notApplicable.length;
    console.log('');
    if (missing.length === 0) {
        if (notApplicable.length === 0) {
            logger.success(`All ${total} required tools available.`);
        }
        else {
            logger.success(`All ${applicable} applicable tools available (${notApplicable.length} n/a for this stack).`);
        }
    }
    else {
        const naSuffix = notApplicable.length > 0 ? `, ${notApplicable.length} n/a` : '';
        logger.warn(`${missing.length}/${applicable} applicable tools missing${naSuffix}.`);
        console.log('');
        // F-UX-3: partition missing by install scope so the hint matches
        // where each tool actually lives. Project-local tools (eslint,
        // @vitest/coverage-v8) are declared in the consumer's package.json
        // — dxkit shouldn't try to add them on the consumer's behalf;
        // `npm ci` already populates `node_modules/.bin/`. Globally-
        // installed tools (semgrep, gitleaks, cloc) are dxkit's
        // responsibility.
        const projectLocalMissing = missing.filter((s) => s.requirement.installScope === 'project-local');
        const globalMissing = missing.filter((s) => s.requirement.installScope !== 'project-local');
        if (projectLocalMissing.length > 0) {
            logger.dim(`${projectLocalMissing.length} project-local tool${projectLocalMissing.length === 1 ? '' : 's'} (${projectLocalMissing.map((s) => s.name).join(', ')}) — run \`npm ci\` (or \`npm install\`) in this repo to provision them from package.json devDependencies.`);
        }
        if (globalMissing.length > 0) {
            logger.dim(`${globalMissing.length} global tool${globalMissing.length === 1 ? '' : 's'} — run \`vyuh-dxkit tools install\` to install interactively.`);
        }
    }
    return statuses;
}
async function confirm(rl, question) {
    const answer = await rl.question(`  ${question} [Y/n]: `);
    if (!answer.trim())
        return true;
    return answer.trim().toLowerCase().startsWith('y');
}
function runInstallCmd(cmd, envOverlay) {
    try {
        // Pick a shell that exists on the platform. On POSIX use bash so
        // multi-command scripts (`&&`, `||`, `;`) work; on Windows omit the
        // option so Node uses the default cmd.exe (the hardcoded `/bin/bash`
        // path doesn't exist there, which made every `tools install` fail
        // outright).
        const shell = process.platform === 'win32' ? undefined : '/bin/bash';
        (0, child_process_1.execSync)(cmd, {
            shell,
            stdio: ['inherit', 'inherit', 'inherit'],
            timeout: 600000, // 10 min for downloads
            env: envOverlay ? { ...process.env, ...envOverlay } : process.env,
        });
        return { success: true, message: 'installed' };
    }
    catch (err) {
        const e = err;
        return { success: false, message: e.message || 'unknown error' };
    }
}
/** Interactive install of missing tools. */
async function runInstall(targetPath, autoYes, options = {}) {
    // Validate single-tool name early so we fail fast with a useful message.
    if (options.toolName && !tool_registry_1.TOOL_DEFS[options.toolName]) {
        logger.fail(`Unknown tool: ${options.toolName}`);
        logger.info('Run `vyuh-dxkit tools list` to see available tools.');
        process.exit(1);
    }
    // Default mode shows the full status table. Targeted modes skip it
    // because the table would be either redundant (single tool) or huge
    // (--all enumerates every TOOL_DEFS entry).
    let statuses;
    let modeLabel;
    if (options.toolName || options.all) {
        const stack = (0, detect_1.detect)(targetPath);
        const names = selectToolNames(stack.languages, options);
        statuses = names
            .map((n) => {
            const def = tool_registry_1.TOOL_DEFS[n];
            return def ? (0, tool_registry_1.findTool)(def, targetPath) : null;
        })
            .filter((s) => s !== null);
        modeLabel = options.toolName
            ? `Install ${options.toolName}`
            : `Install all known tools (${statuses.length} candidates, missing only)`;
    }
    else {
        statuses = showStatus(targetPath);
        modeLabel = 'Install missing tools';
    }
    // Exclude n/a entries — applicability gate already determined the
    // tool doesn't apply to this stack, so attempting an install would
    // either no-op or pull in dead weight.
    const missing = statuses.filter((s) => !s.available && s.source !== 'n/a');
    const notApplicable = statuses.filter((s) => s.source === 'n/a');
    // Targeted single-tool install hitting an n/a tool: surface the
    // reason and exit cleanly rather than silently skipping. Users who
    // explicitly typed the name deserve a direct answer.
    if (options.toolName && notApplicable.length === 1 && missing.length === 0) {
        const reason = notApplicable[0].notApplicableReason ?? 'not applicable to this stack';
        logger.info(`${options.toolName} is not applicable here: ${reason}.`);
        return;
    }
    if (missing.length === 0) {
        if (options.toolName) {
            logger.success(`${options.toolName} is already installed.`);
        }
        else if (options.all) {
            const naSuffix = notApplicable.length > 0 ? ` (${notApplicable.length} n/a for this stack — skipped)` : '';
            logger.success(`All ${statuses.length - notApplicable.length} applicable tools already installed.${naSuffix}`);
        }
        else if (notApplicable.length > 0) {
            logger.success(`All applicable tools available (${notApplicable.length} n/a for this stack — skipped).`);
        }
        return;
    }
    console.log('');
    logger.header(modeLabel);
    const rl = autoYes ? null : readline.createInterface({ input: process_1.stdin, output: process_1.stdout });
    const results = [];
    try {
        for (const s of missing) {
            const def = tool_registry_1.TOOL_DEFS[s.name];
            if (!def) {
                results.push({ name: s.name, status: 'skipped', msg: 'no install command' });
                continue;
            }
            const cmd = (0, tool_registry_1.getInstallCommand)(def);
            if (cmd === 'builtin' || cmd === 'builtin (npm)' || cmd === 'builtin (dotnet SDK)') {
                results.push({ name: s.name, status: 'skipped', msg: 'builtin' });
                continue;
            }
            console.log('');
            console.log(`  ${logger.bold(s.name)} — ${def.description}`);
            logger.dim(`    ${cmd}`);
            let shouldInstall = autoYes;
            if (!autoYes && rl) {
                shouldInstall = await confirm(rl, `  Install ${s.name}?`);
            }
            if (!shouldInstall) {
                results.push({ name: s.name, status: 'skipped', msg: 'user declined' });
                logger.dim('  Skipped.');
                continue;
            }
            console.log('');
            logger.info(`Running: ${cmd}`);
            const result = runInstallCmd(cmd, (0, tool_registry_1.getInstallEnv)(targetPath));
            if (result.success) {
                // Verify install worked. An install command can legitimately
                // exit 0 without producing the binary — e.g. the vitest-coverage
                // guard no-ops when vitest isn't a target-repo dep. Treat
                // "exit 0 + tool still missing" as a skip, not a failure: the
                // script ran cleanly, we just didn't get the binary we wanted.
                // Real install failures surface through the `result.success ===
                // false` branch below (non-zero exit).
                const recheck = (0, tool_registry_1.findTool)(def, targetPath);
                if (recheck.available) {
                    results.push({ name: s.name, status: 'installed' });
                    logger.success(`${s.name} installed (${recheck.source})`);
                }
                else {
                    results.push({
                        name: s.name,
                        status: 'skipped',
                        msg: 'install command exited 0 without producing the binary (likely a guarded no-op)',
                    });
                    logger.dim(`${s.name} skipped — install command exited cleanly without installing`);
                }
            }
            else {
                results.push({ name: s.name, status: 'failed', msg: result.message });
                logger.fail(`${s.name}: ${result.message}`);
            }
        }
    }
    finally {
        rl?.close();
    }
    // Summary
    console.log('');
    logger.header('Summary');
    const installed = results.filter((r) => r.status === 'installed').length;
    const skipped = results.filter((r) => r.status === 'skipped').length;
    const failed = results.filter((r) => r.status === 'failed').length;
    const naSuffix = notApplicable.length > 0 ? `, ${notApplicable.length} n/a` : '';
    logger.info(`${installed} installed, ${skipped} skipped, ${failed} failed${naSuffix}`);
    if (failed > 0) {
        console.log('');
        logger.dim('Failed installs:');
        for (const r of results.filter((r) => r.status === 'failed')) {
            logger.dim(`  ${r.name}: ${r.msg}`);
        }
    }
    if (installed > 0) {
        console.log('');
        logger.dim('Run `vyuh-dxkit health` to use the newly installed tools.');
    }
}
async function runToolsCommand(targetPath, subCommand, autoYes, options = {}) {
    if (!subCommand || subCommand === 'list' || subCommand === 'status') {
        showStatus(targetPath);
        return;
    }
    if (subCommand === 'install') {
        await runInstall(targetPath, autoYes, options);
        return;
    }
    logger.fail(`Unknown tools subcommand: ${subCommand}`);
    logger.info('Usage: vyuh-dxkit tools [list|install] [<tool-name>] [path] [--all] [--yes]');
    process.exit(1);
}
//# sourceMappingURL=tools-cli.js.map