import { spawnSync } from 'node:child_process';
const ALWAYS_KEEP = new Set([
    'release:version-truth',
    'release:dag-full-coverage',
    'runtime:ts-source-of-truth',
    'typecheck',
    'schema:check'
]);
export function selectAffectedReleaseGates(root, manifest, gates, input = {}) {
    if (input.full) {
        return selectionResult(gates, gates, [], 'full', {}, []);
    }
    const changedFiles = input.changedFiles ? [...new Set(input.changedFiles)].sort() : resolveChangedFiles(root, input.changedSince || 'auto');
    const selected = [];
    const reasons = {};
    for (const gate of gates) {
        const reason = gateSelectionReason(gate, changedFiles, input.preset || 'affected');
        if (reason) {
            selected.push(gate);
            reasons[gate.id] = reason;
        }
    }
    const byId = new Map(gates.map((gate) => [gate.id, gate]));
    for (const id of ALWAYS_KEEP) {
        const gate = byId.get(id) || manifest.gates.find((candidate) => candidate.id === id);
        if (gate && !selected.some((row) => row.id === gate.id)) {
            selected.push(gate);
            reasons[gate.id] = 'always_keep_core_release_safety';
        }
    }
    const expanded = input.preset === 'affected' || input.preset === 'fast'
        ? selected
        : expandWithDependencies(selected, manifest);
    const ordered = manifest.gates.filter((gate) => expanded.some((row) => row.id === gate.id));
    return selectionResult(gates, ordered, changedFiles, 'affected', reasons, gates.filter((gate) => !ordered.some((row) => row.id === gate.id)).map((gate) => gate.id));
}
function expandWithDependencies(selected, manifest) {
    const byId = new Map(manifest.gates.map((gate) => [gate.id, gate]));
    const out = new Map(selected.map((gate) => [gate.id, gate]));
    const visit = (gate) => {
        for (const dep of gate.deps || []) {
            const depGate = byId.get(dep);
            if (depGate && !out.has(depGate.id)) {
                out.set(depGate.id, depGate);
                visit(depGate);
            }
        }
    };
    for (const gate of selected)
        visit(gate);
    return [...out.values()];
}
function gateSelectionReason(gate, changedFiles, preset) {
    if (ALWAYS_KEEP.has(gate.id))
        return 'always_keep_core_release_safety';
    if (!changedFiles.length)
        return preset === 'fast' ? 'fast_no_diff_core_only_skip' : 'no_changed_files';
    const releaseGate = /^(release:|publish:|prepublish)/.test(gate.id);
    if (changedFiles.some((file) => file === 'package.json' || file === 'package-lock.json')) {
        if (/^(release:|publish:|prepublish|runtime:|typecheck|schema:check)/.test(gate.id))
            return 'package_metadata_changed';
    }
    if (changedFiles.some((file) => file === 'release-gates.v2.json' || file.startsWith('src/core/release/'))) {
        if (releaseGate)
            return 'release_gate_system_changed';
    }
    const matchingReleaseScript = changedFiles.some((file) => releaseScriptGateCandidates(file).includes(gate.id));
    if (matchingReleaseScript)
        return 'release_script_changed';
    if (changedFiles.some((file) => file.startsWith('src/scripts/prepublish-') || file.startsWith('src/scripts/publish-'))) {
        if (releaseGate && gate.id === 'release:version-truth')
            return 'publish_or_prepublish_script_changed';
    }
    if (changedFiles.some((file) => file.startsWith('src/scripts/scheduler-') || file.startsWith('src/core/scheduler/'))) {
        return gate.id.startsWith('scheduler:') ? 'scheduler_source_changed' : null;
    }
    if (changedFiles.some((file) => file.startsWith('src/core/research/')))
        return gate.id.startsWith('research:') ? 'research_source_changed' : null;
    if (changedFiles.some((file) => file.startsWith('src/core/zellij/') || file.startsWith('src/commands/zellij')))
        return gate.id.startsWith('zellij:') || gate.id.startsWith('agent:zellij') || gate.id.startsWith('naruto:zellij') ? 'zellij_source_changed' : null;
    if (changedFiles.some((file) => file.includes('/db') || file.includes('mad-db') || file.includes('mcp')))
        return /db|mcp|mad-db|mad-sks/.test(gate.id) ? 'db_mcp_or_mad_db_changed' : null;
    const inputs = (gate.cache?.inputs || []).filter((pattern) => !isBroadAffectedInput(pattern));
    if (inputs.some((pattern) => changedFiles.some((file) => matchesGlobish(file, pattern))))
        return 'cache_input_changed';
    return null;
}
function selectionResult(all, selected, changedFiles, mode, reasons, skipped) {
    return {
        gates: selected,
        selection: {
            schema: 'sks.release-gate-affected-selection.v1',
            mode,
            changed_files: changedFiles,
            selected_gate_ids: selected.map((gate) => gate.id),
            skipped_gate_ids: skipped.length ? skipped : all.filter((gate) => !selected.some((row) => row.id === gate.id)).map((gate) => gate.id),
            reasons
        }
    };
}
function resolveChangedFiles(root, changedSince) {
    const base = changedSince === 'auto' ? 'HEAD' : changedSince;
    const args = base === 'HEAD'
        ? ['diff', '--name-only', 'HEAD']
        : ['diff', '--name-only', String(base)];
    const diff = spawnSync('git', args, { cwd: root, encoding: 'utf8' });
    const status = spawnSync('git', ['status', '--short'], { cwd: root, encoding: 'utf8' });
    const files = [
        ...String(diff.stdout || '').split(/\n/),
        ...String(status.stdout || '').split(/\n/).map((line) => line.slice(3))
    ].map((file) => file.trim()).filter(Boolean);
    return [...new Set(files)].sort();
}
function matchesGlobish(file, pattern) {
    const normalized = pattern.replace(/\\/g, '/');
    if (normalized === file)
        return true;
    if (normalized.endsWith('/**'))
        return file.startsWith(normalized.slice(0, -3));
    if (normalized.endsWith('/**/*'))
        return file.startsWith(normalized.slice(0, -5));
    if (normalized.includes('**'))
        return file.startsWith(normalized.split('**')[0] || '');
    if (normalized.endsWith('*'))
        return file.startsWith(normalized.slice(0, -1));
    return false;
}
function isBroadAffectedInput(pattern) {
    const normalized = pattern.replace(/\\/g, '/');
    return new Set([
        '**',
        '**/*',
        'src/**',
        'src/**/*',
        'schemas/**',
        'schemas/**/*',
        'package.json',
        'package-lock.json',
        'release-gates.v2.json'
    ]).has(normalized);
}
function releaseScriptGateCandidates(file) {
    const normalized = file.replace(/\\/g, '/');
    const base = normalized.split('/').pop()?.replace(/\.(ts|js|mjs|cjs)$/, '') || '';
    if (!base.startsWith('release-'))
        return [];
    const rest = base.slice('release-'.length);
    const withoutCheck = rest.replace(/-check$/, '');
    return [
        `release:${rest}`,
        `release:${withoutCheck}`,
        `release:${withoutCheck}:check`
    ];
}
//# sourceMappingURL=release-gate-affected-selector.js.map