"use strict";
/**
 * `vyuh-dxkit allowlist <subcommand>` — orchestrates the user-facing
 * write/read paths over the allowlist module.
 *
 * Subcommands (Sprint 1 chunk):
 *
 *   - `add <file>:<line>` — inline annotation insertion. Kind-agnostic;
 *     the annotation grammar carries category + reason only. Refuses
 *     non-inline-compatible categories (accepted-risk / deferred).
 *
 *   - `add --fingerprint=<id> --kind=<kind>` — file-level allowlist
 *     entry. Persists to `.dxkit/allowlist.json` (or its sanitized
 *     mode + gitignored reasons sidecar). Required for any
 *     accepted-risk / deferred suppression OR any kind that lacks a
 *     stable single-line attachment point.
 *
 *   - `list` — print every entry across the file-level allowlist.
 *     Reads only; no mutation. Honors `--json` for structured output.
 *
 *   - `show <fingerprint>` — print one entry's full detail. Falls
 *     back to a "no entry found" message when the fingerprint isn't
 *     present.
 *
 * Subcommands `audit` and `prune` land in a follow-up commit.
 *
 * # Architectural posture
 *
 * Every IO goes through `loadAllowlist` / `saveAllowlist` in
 * `src/allowlist/file.ts` (arch-rule 1 enforces this). Inline
 * annotation insertion goes through `insertAnnotation` in
 * `src/allowlist/inline.ts`. Per-kind / per-category validation
 * goes through `categories.ts` helpers. NO duplicated taxonomy or
 * IO logic here — this file is pure orchestration.
 */
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.ALLOWLIST_FILENAME = exports.DEFAULT_EXPIRY_DAYS = exports.ALLOWLIST_SUBCOMMANDS = void 0;
exports.runAllowlist = runAllowlist;
exports.runAllowlistAdd = runAllowlistAdd;
exports.runAllowlistList = runAllowlistList;
exports.runAllowlistShow = runAllowlistShow;
exports.runAllowlistAudit = runAllowlistAudit;
exports.runAllowlistPrune = runAllowlistPrune;
const path = __importStar(require("path"));
const child_process_1 = require("child_process");
const logger = __importStar(require("../logger"));
const languages_1 = require("../languages");
const categories_1 = require("./categories");
Object.defineProperty(exports, "DEFAULT_EXPIRY_DAYS", { enumerable: true, get: function () { return categories_1.DEFAULT_EXPIRY_DAYS; } });
const file_1 = require("./file");
Object.defineProperty(exports, "ALLOWLIST_FILENAME", { enumerable: true, get: function () { return file_1.ALLOWLIST_FILENAME; } });
const inline_1 = require("./inline");
/** Subcommands recognized under `vyuh-dxkit allowlist`. */
exports.ALLOWLIST_SUBCOMMANDS = ['add', 'list', 'show', 'audit', 'prune'];
/**
 * Dispatch entry point called from `src/cli.ts`. Validates the
 * subcommand name + routes to the per-subcommand handler. Unknown
 * subcommands exit with a clear error and the list of recognized
 * names.
 */
async function runAllowlist(cwd, subcommand, args) {
    if (!subcommand || !isAllowlistSubcommand(subcommand)) {
        logger.fail(`Unknown allowlist subcommand: ${JSON.stringify(subcommand ?? '(none)')}. ` +
            `Expected one of: ${exports.ALLOWLIST_SUBCOMMANDS.join(', ')}.`);
        process.exit(1);
    }
    switch (subcommand) {
        case 'add':
            return runAllowlistAdd(cwd, {
                target: args.positionalAfter,
                category: args.values.category,
                reason: args.values.reason,
                kind: args.values.kind,
                fingerprint: args.values.fingerprint,
                expires: args.values.expires,
                acknowledgedSeverity: args.values['acknowledged-severity'],
                addedBy: args.values['added-by'],
                mode: args.values.mode,
            });
        case 'list':
            return runAllowlistList(cwd, { json: !!args.values.json });
        case 'show':
            return runAllowlistShow(cwd, {
                fingerprint: args.positionalAfter,
                json: !!args.values.json,
            });
        case 'audit': {
            const horizonRaw = args.values['soon-days'];
            const horizon = horizonRaw ? parseInt(horizonRaw, 10) : undefined;
            return runAllowlistAudit(cwd, {
                json: !!args.values.json,
                soonToExpireDays: Number.isFinite(horizon) ? horizon : undefined,
            });
        }
        case 'prune':
            return runAllowlistPrune(cwd, {
                json: !!args.values.json,
                dryRun: !!args.values['dry-run'],
                yes: !!args.values.yes,
            });
    }
}
// ─── add ──────────────────────────────────────────────────────────────────
async function runAllowlistAdd(cwd, opts) {
    // Validate category up-front so the rest of the flow can assume
    // it's a canonical value.
    const category = parseCategory(opts.category);
    const reason = (opts.reason ?? '').trim();
    if (!reason) {
        logger.fail('--reason is required (non-empty rationale string)');
        process.exit(1);
    }
    // Two routing paths: inline annotation insertion vs file-level entry.
    // The target shape decides:
    //   - `<file>:<line>` → inline (category must be inline-compatible)
    //   - `--fingerprint=<id> --kind=<kind>` → file-level
    const inlineTarget = parseInlineTarget(opts.target);
    if (inlineTarget) {
        return runAddInline({ cwd, target: inlineTarget, category, reason });
    }
    // File-level form
    return runAddFileLevel({ cwd, opts, category, reason });
}
function parseInlineTarget(target) {
    if (!target)
        return null;
    const m = target.match(/^(.+):(\d+)$/);
    if (!m)
        return null;
    return { file: m[1], line: parseInt(m[2], 10) };
}
async function runAddInline(args) {
    const { cwd, target, category, reason } = args;
    if (!categories_1.INLINE_COMPATIBLE_CATEGORIES.has(category)) {
        logger.fail(`category ${JSON.stringify(category)} is file-only — ` +
            `use --fingerprint=<id> --kind=<kind> form instead. ` +
            `Inline-compatible categories: ${[...categories_1.INLINE_COMPATIBLE_CATEGORIES].join(', ')}.`);
        process.exit(1);
    }
    const absPath = path.resolve(cwd, target.file);
    const lang = inferLanguage(target.file);
    if (!lang || !lang.commentSyntax) {
        logger.fail(`cannot infer language from file extension for ${JSON.stringify(target.file)}; ` +
            `inline annotation requires a known language pack with commentSyntax`);
        process.exit(1);
    }
    const result = (0, inline_1.insertAnnotation)(absPath, target.line, { category, reason }, lang);
    logger.info(`Inserted ${result.position} allowlist annotation at ${target.file}:${result.annotationLine} ` +
        `(category=${category})`);
}
async function runAddFileLevel(args) {
    const { cwd, opts, category, reason } = args;
    const fingerprint = opts.fingerprint?.trim();
    const kindRaw = opts.kind?.trim();
    if (!fingerprint || !kindRaw) {
        logger.fail(`file-level allowlist entry requires --fingerprint=<16-hex> and --kind=<kind> ` +
            `(or pass <file>:<line> for inline annotation when kind+category are inline-compatible)`);
        process.exit(1);
    }
    const kind = kindRaw;
    if (!(0, categories_1.isCategoryValidForKind)(kind, category)) {
        logger.fail(`category ${JSON.stringify(category)} does not apply to kind ${JSON.stringify(kind)}`);
        process.exit(1);
    }
    const expiresAt = resolveExpiresAt(opts.expires, category);
    const addedBy = opts.addedBy?.trim() || resolveGitUserEmail(cwd);
    if (!addedBy) {
        logger.fail(`--added-by is required (or set git config user.email so it can be inferred)`);
        process.exit(1);
    }
    const addedAt = todayISO();
    const acknowledgedSeverity = parseSeverityOpt(opts.acknowledgedSeverity);
    const entry = {
        fingerprint,
        kind,
        category,
        reason,
        addedBy,
        addedAt,
        ...(expiresAt !== undefined ? { expiresAt } : {}),
        ...(acknowledgedSeverity !== undefined ? { acknowledgedSeverity } : {}),
    };
    // Resolve effective mode (CLI override → existing file mode → default 'full').
    const mode = resolveMode(cwd, opts.mode);
    const validationErrors = (0, file_1.validateAllowlistEntry)(entry, mode);
    if (validationErrors.length > 0) {
        logger.fail(`allowlist entry failed validation:`);
        for (const e of validationErrors) {
            logger.fail(`  - ${e.field}: ${e.message}`);
        }
        process.exit(1);
    }
    const existing = (0, file_1.loadAllowlist)(cwd) ?? (0, file_1.emptyAllowlistFile)(mode);
    if ((0, file_1.findEntry)(existing, fingerprint)) {
        logger.fail(`allowlist already contains entry for fingerprint ${fingerprint}. ` +
            `Run \`vyuh-dxkit allowlist show ${fingerprint}\` to inspect, or remove first.`);
        process.exit(1);
    }
    const updated = { ...(0, file_1.addEntry)(existing, entry), mode };
    (0, file_1.saveAllowlist)(cwd, updated);
    logger.info(`Added allowlist entry for fingerprint ${fingerprint} (kind=${kind}, category=${category})` +
        (expiresAt ? `, expires ${expiresAt}` : ''));
}
// ─── list ─────────────────────────────────────────────────────────────────
async function runAllowlistList(cwd, opts) {
    const file = (0, file_1.loadAllowlist)(cwd);
    if (opts.json) {
        process.stdout.write(JSON.stringify(file ?? (0, file_1.emptyAllowlistFile)('full'), null, 2) + '\n');
        return;
    }
    if (!file || file.entries.length === 0) {
        logger.info(`No allowlist entries. Run \`vyuh-dxkit allowlist add\` to create one.`);
        return;
    }
    logger.info(`${file.entries.length} allowlist entr${file.entries.length === 1 ? 'y' : 'ies'} ` +
        `(mode=${file.mode}, schema=${file.schemaVersion}):`);
    for (const entry of file.entries) {
        const expires = entry.expiresAt ? ` · expires ${entry.expiresAt}` : '';
        const reasonPreview = entry.reason ? ` — ${truncate(entry.reason, 60)}` : '';
        logger.info(`  ${entry.fingerprint}  ${entry.kind}/${entry.category}` +
            `  (added ${entry.addedAt}${expires})${reasonPreview}`);
    }
}
// ─── show ─────────────────────────────────────────────────────────────────
async function runAllowlistShow(cwd, opts) {
    const fp = opts.fingerprint?.trim();
    if (!fp) {
        logger.fail(`Usage: vyuh-dxkit allowlist show <fingerprint>`);
        process.exit(1);
    }
    const file = (0, file_1.loadAllowlist)(cwd);
    if (!file) {
        logger.fail(`No allowlist file at ${(0, file_1.pathForAllowlist)(cwd)}`);
        process.exit(1);
    }
    const entry = (0, file_1.findEntry)(file, fp);
    if (!entry) {
        logger.fail(`No allowlist entry for fingerprint ${fp}`);
        process.exit(1);
    }
    if (opts.json) {
        process.stdout.write(JSON.stringify(entry, null, 2) + '\n');
        return;
    }
    logger.info(`Fingerprint:        ${entry.fingerprint}`);
    logger.info(`Kind:               ${entry.kind}`);
    logger.info(`Category:           ${entry.category}`);
    logger.info(`Added at:           ${entry.addedAt}`);
    if (entry.addedBy)
        logger.info(`Added by:           ${entry.addedBy}`);
    if (entry.expiresAt)
        logger.info(`Expires at:         ${entry.expiresAt}`);
    if (entry.acknowledgedSeverity) {
        logger.info(`Acknowledged sev.:  ${entry.acknowledgedSeverity}`);
    }
    if (entry.reason)
        logger.info(`Reason:             ${entry.reason}`);
}
// ─── audit ────────────────────────────────────────────────────────────────
async function runAllowlistAudit(cwd, opts) {
    const file = (0, file_1.loadAllowlist)(cwd);
    if (!file) {
        if (opts.json) {
            process.stdout.write(JSON.stringify({ expired: [], soonToExpire: [], missingRationale: [] }, null, 2) + '\n');
            return;
        }
        logger.info(`No allowlist file at ${(0, file_1.pathForAllowlist)(cwd)} — nothing to audit.`);
        return;
    }
    const report = (0, file_1.auditAllowlist)(file, { soonToExpireDays: opts.soonToExpireDays });
    if (opts.json) {
        process.stdout.write(JSON.stringify(report, null, 2) + '\n');
        return;
    }
    const total = file.entries.length;
    const horizon = opts.soonToExpireDays ?? 14;
    logger.info(`Allowlist audit: ${total} entr${total === 1 ? 'y' : 'ies'} ` +
        `(mode=${file.mode}); soon-to-expire window=${horizon} days`);
    if (report.expired.length === 0 &&
        report.soonToExpire.length === 0 &&
        report.missingRationale.length === 0) {
        logger.success(`No issues found.`);
        return;
    }
    if (report.expired.length > 0) {
        logger.warn(`Expired (${report.expired.length}) — run \`vyuh-dxkit allowlist prune\` to remove:`);
        for (const e of report.expired) {
            logger.info(`  ${e.fingerprint}  ${e.kind}/${e.category}  expired ${e.expiresAt}`);
        }
    }
    if (report.soonToExpire.length > 0) {
        logger.warn(`Soon to expire (${report.soonToExpire.length}; within ${horizon} days) — review or extend:`);
        for (const { entry, daysRemaining } of report.soonToExpire) {
            logger.info(`  ${entry.fingerprint}  ${entry.kind}/${entry.category}` +
                `  expires ${entry.expiresAt} (in ${daysRemaining}d)`);
        }
    }
    if (report.missingRationale.length > 0) {
        logger.warn(`Missing rationale (${report.missingRationale.length}) — ` +
            `add a reason or sync the gitignored reasons sidecar:`);
        for (const e of report.missingRationale) {
            logger.info(`  ${e.fingerprint}  ${e.kind}/${e.category}`);
        }
    }
}
// ─── prune ────────────────────────────────────────────────────────────────
async function runAllowlistPrune(cwd, opts) {
    const file = (0, file_1.loadAllowlist)(cwd);
    if (!file) {
        logger.info(`No allowlist file at ${(0, file_1.pathForAllowlist)(cwd)} — nothing to prune.`);
        return;
    }
    const { kept, removed } = (0, file_1.pruneExpired)(file);
    if (opts.json) {
        process.stdout.write(JSON.stringify({ dryRun: !!opts.dryRun, removed, keptCount: kept.entries.length }, null, 2) +
            '\n');
        if (!opts.dryRun && removed.length > 0)
            (0, file_1.saveAllowlist)(cwd, kept);
        return;
    }
    if (removed.length === 0) {
        logger.info(`No expired entries — allowlist is clean.`);
        return;
    }
    const verb = opts.dryRun ? 'Would remove' : 'Removing';
    logger.warn(`${verb} ${removed.length} expired entr${removed.length === 1 ? 'y' : 'ies'}:`);
    for (const e of removed) {
        logger.info(`  ${e.fingerprint}  ${e.kind}/${e.category}  expired ${e.expiresAt}`);
    }
    if (opts.dryRun) {
        logger.info(`(dry-run — no changes written; rerun without --dry-run to apply)`);
        return;
    }
    (0, file_1.saveAllowlist)(cwd, kept);
    logger.success(`Pruned ${removed.length} expired entries.`);
}
// ─── Internals ────────────────────────────────────────────────────────────
function isAllowlistSubcommand(value) {
    return exports.ALLOWLIST_SUBCOMMANDS.includes(value);
}
function parseCategory(raw) {
    if (!raw) {
        logger.fail(`--category is required. One of: ${categories_1.ALL_CATEGORIES.join(', ')}.`);
        process.exit(1);
    }
    if (!categories_1.ALL_CATEGORIES.includes(raw)) {
        logger.fail(`--category ${JSON.stringify(raw)} is not a known category. ` +
            `One of: ${categories_1.ALL_CATEGORIES.join(', ')}.`);
        process.exit(1);
    }
    return raw;
}
const VALID_SEVERITIES = ['critical', 'high', 'medium', 'low'];
function parseSeverityOpt(raw) {
    if (raw === undefined)
        return undefined;
    if (!VALID_SEVERITIES.includes(raw)) {
        logger.fail(`--acknowledged-severity ${JSON.stringify(raw)} is not a known severity. ` +
            `One of: ${VALID_SEVERITIES.join(', ')}.`);
        process.exit(1);
    }
    return raw;
}
function resolveExpiresAt(raw, category) {
    if (raw !== undefined) {
        if (!/^\d{4}-\d{2}-\d{2}$/.test(raw)) {
            logger.fail(`--expires must be ISO date YYYY-MM-DD; got ${JSON.stringify(raw)}`);
            process.exit(1);
        }
        return raw;
    }
    if ((0, categories_1.requiresExpiry)(category)) {
        // Default to DEFAULT_EXPIRY_DAYS from today
        return (0, categories_1.defaultExpiryDate)(new Date());
    }
    return undefined;
}
function resolveMode(cwd, override) {
    if (override !== undefined) {
        if (!file_1.ALL_MODES.includes(override)) {
            logger.fail(`--mode ${JSON.stringify(override)} is not a known mode. ` +
                `One of: ${file_1.ALL_MODES.join(', ')}.`);
            process.exit(1);
        }
        return override;
    }
    const existing = (0, file_1.loadAllowlist)(cwd);
    return existing?.mode ?? 'full';
}
function resolveGitUserEmail(cwd) {
    try {
        const out = (0, child_process_1.execSync)('git config --get user.email', {
            cwd,
            stdio: ['ignore', 'pipe', 'ignore'],
            encoding: 'utf8',
        }).trim();
        return out || undefined;
    }
    catch {
        return undefined;
    }
}
function inferLanguage(file) {
    const ext = path.extname(file).toLowerCase();
    if (!ext)
        return undefined;
    for (const lang of languages_1.LANGUAGES) {
        if (lang.sourceExtensions.includes(ext))
            return lang;
    }
    return undefined;
}
function todayISO(now = new Date()) {
    return now.toISOString().slice(0, 10);
}
function truncate(s, max) {
    if (s.length <= max)
        return s;
    return s.slice(0, max - 1) + '…';
}
//# sourceMappingURL=cli.js.map