#!/usr/bin/env node
import { spawnSync } from 'node:child_process';
import { existsSync, readdirSync, readFileSync } from 'node:fs';
import { availableParallelism } from 'node:os';
import path from 'node:path';
// Cap any requested --test-concurrency to the available cores. node:test does NOT
// auto-cap an explicit --test-concurrency, so a hardcoded `=8` oversubscribes a
// 2-core CI runner and produces broad timeout-shaped flakes across subprocess /
// timing-sensitive tests. Clamping (never raising) keeps c=8 on capable dev
// machines while making CI run at its core count. (R-TCC-1)
function clampTestConcurrency(args) {
    const cap = Math.max(1, availableParallelism());
    const out = [];
    for (let i = 0; i < args.length; i += 1) {
        const arg = args[i];
        const eq = /^--test-concurrency=(\d+)$/.exec(arg);
        if (eq) {
            out.push(`--test-concurrency=${Math.min(Number(eq[1]), cap)}`);
            continue;
        }
        if (arg === '--test-concurrency' && /^\d+$/.test(args[i + 1] ?? '')) {
            out.push(arg, String(Math.min(Number(args[i + 1]), cap)));
            i += 1;
            continue;
        }
        out.push(arg);
    }
    return out;
}
const VALID_TIERS = new Set(['fast', 'integration', 'expensive', 'contract']);
const QUARANTINED_TIER_EXCLUSIONS = new Set(['fast', 'integration']);
const DEFAULT_TEST_RUNNER_TIMEOUT_MS = 30 * 60 * 1000;
const MAX_TEST_RUNNER_TIMEOUT_MS = 24 * 60 * 60 * 1000;
function exitWithError(message, code) {
    process.stderr.write(`${message}\n`);
    process.exit(code);
}
function requireArgValue(args, index, flag, code = 2) {
    const value = args[index + 1];
    if (!value)
        exitWithError(`Missing value for ${flag}`, code);
    return value;
}
function parseManifestMode(value) {
    if (value === 'include' || value === 'exclude') {
        return value;
    }
    exitWithError(`Unknown manifest mode: ${value}`, 2);
}
function parseTier(value) {
    if (VALID_TIERS.has(value)) {
        return value;
    }
    exitWithError(`Unknown tier: ${value}`, 2);
}
function parseArgs(args) {
    const runnerArgs = [];
    const testFiles = [];
    let dryRun = false;
    let grepPattern = null;
    let manifestMode = null;
    let manifestPath = null;
    let tier = null;
    for (let index = 0; index < args.length; index += 1) {
        const arg = args[index];
        switch (arg) {
            case '--grep': {
                const pattern = requireArgValue(args, index, '--grep', 1);
                grepPattern = pattern;
                runnerArgs.push('--test-name-pattern', pattern);
                index += 1;
                break;
            }
            case '--tier':
                tier = parseTier(requireArgValue(args, index, '--tier'));
                index += 1;
                break;
            case '--dry-run':
                dryRun = true;
                break;
            case '--manifest':
                manifestPath = requireArgValue(args, index, '--manifest');
                index += 1;
                break;
            case '--manifest-mode':
                manifestMode = parseManifestMode(requireArgValue(args, index, '--manifest-mode'));
                index += 1;
                break;
            default:
                if (arg.startsWith('--'))
                    runnerArgs.push(arg);
                else
                    testFiles.push(arg);
                break;
        }
    }
    if (tier && testFiles.length > 0) {
        exitWithError('--tier cannot be combined with positional test files', 2);
    }
    if ((manifestPath === null) !== (manifestMode === null)) {
        exitWithError('--manifest and --manifest-mode must be provided together', 2);
    }
    return { dryRun, grepPattern, manifestMode, manifestPath, runnerArgs, testFiles, tier };
}
function normalizeTestPath(filePath) {
    return filePath.split(path.sep).join('/');
}
function discoverTestFiles(dir, rootDir) {
    if (!existsSync(dir))
        return [];
    return readdirSync(dir, { withFileTypes: true })
        .flatMap((entry) => {
        const fullPath = path.join(dir, entry.name);
        if (entry.isDirectory())
            return discoverTestFiles(fullPath, rootDir);
        if (!entry.isFile() || !entry.name.endsWith('.test.js'))
            return [];
        return [normalizeTestPath(path.relative(rootDir, fullPath))];
    })
        .sort();
}
function firstMeaningfulLine(filePath) {
    const lines = readFileSync(filePath, 'utf8').split(/\r?\n/);
    for (const line of lines) {
        if (line.startsWith('#!'))
            continue;
        if (line.trim() === '')
            continue;
        return line.trim();
    }
    return '';
}
function tierForTestFile(filePath) {
    const match = firstMeaningfulLine(filePath).match(/^\/\/\s*@tier:\s*([A-Za-z0-9_-]+)\s*$/);
    return match?.[1] ?? null;
}
function normalizeQuarantineEntry(rawEntry) {
    const withoutDotSlash = rawEntry.replace(/^\.\//, '');
    if (withoutDotSlash.startsWith('tests/'))
        return withoutDotSlash;
    return `tests/${withoutDotSlash}`;
}
function readManifestEntries(rootDir, manifestPath) {
    const resolvedPath = path.resolve(rootDir, manifestPath);
    if (!existsSync(resolvedPath)) {
        exitWithError(`Manifest not found: ${manifestPath}`, 1);
    }
    let parsed;
    try {
        parsed = JSON.parse(readFileSync(resolvedPath, 'utf8'));
    }
    catch (error) {
        const reason = error instanceof Error ? error.message : String(error);
        exitWithError(`Manifest is not valid JSON: ${manifestPath}\n${reason}`, 1);
    }
    if (typeof parsed !== 'object' || parsed === null || !('entries' in parsed)) {
        exitWithError(`Manifest must contain an entries array: ${manifestPath}`, 1);
    }
    const { entries } = parsed;
    if (!Array.isArray(entries) || !entries.every((entry) => typeof entry === 'string')) {
        exitWithError(`Manifest entries must be string[]: ${manifestPath}`, 1);
    }
    const normalized = new Set();
    for (const entry of entries) {
        const candidate = normalizeQuarantineEntry(entry);
        const candidatePath = path.resolve(rootDir, candidate);
        if (!existsSync(candidatePath)) {
            exitWithError(`Manifest entry not found: ${candidate}`, 1);
        }
        normalized.add(normalizeTestPath(candidate));
    }
    return normalized;
}
function readQuarantineSet(rootDir) {
    const manifestPath = path.join(rootDir, 'tests', 'QUARANTINE.md');
    if (!existsSync(manifestPath))
        return new Set();
    const entries = new Set();
    const manifest = readFileSync(manifestPath, 'utf8');
    const entryPattern = /((?:\.\/)?(?:tests\/)?[A-Za-z0-9._/@+-]+\.test\.js)/g;
    for (const line of manifest.split(/\r?\n/)) {
        let match;
        while ((match = entryPattern.exec(line)) !== null) {
            entries.add(normalizeQuarantineEntry(match[1].trim()));
        }
    }
    return entries;
}
function discoverTierFiles(rootDir, tier) {
    const testsDir = path.join(rootDir, 'tests');
    const quarantineSet = QUARANTINED_TIER_EXCLUSIONS.has(tier)
        ? readQuarantineSet(rootDir)
        : new Set();
    return discoverTestFiles(testsDir, rootDir).filter((relativePath) => {
        if (quarantineSet.has(relativePath))
            return false;
        return tierForTestFile(path.join(rootDir, relativePath)) === tier;
    });
}
function applyManifestFilter(selectedFiles, manifestEntries, manifestMode) {
    return selectedFiles.filter((relativePath) => {
        const inManifest = manifestEntries.has(normalizeTestPath(relativePath));
        return manifestMode === 'include' ? inManifest : !inManifest;
    });
}
function shouldSkipTier(tier) {
    return tier === 'expensive' && process.env.RUN_EXPENSIVE_TESTS !== '1';
}
function getRunnerTimeoutMs() {
    const raw = process.env.PICKLE_TEST_RUNNER_TIMEOUT_MS;
    if (raw === undefined || raw.trim() === '')
        return DEFAULT_TEST_RUNNER_TIMEOUT_MS;
    const parsed = Number.parseInt(raw, 10);
    if (!Number.isFinite(parsed) || parsed <= 0) {
        exitWithError(`Invalid PICKLE_TEST_RUNNER_TIMEOUT_MS: ${raw}`, 2);
    }
    return Math.min(parsed, MAX_TEST_RUNNER_TIMEOUT_MS);
}
function selectFiles(rootDir, tier, grepPattern, testFiles, manifestEntries, manifestMode) {
    const baseSelection = tier
        ? discoverTierFiles(rootDir, tier)
        : grepPattern
            ? testFiles.filter((file) => readFileSync(file, 'utf8').includes(grepPattern))
            : testFiles;
    if (manifestEntries && manifestMode) {
        return applyManifestFilter(baseSelection, manifestEntries, manifestMode);
    }
    return baseSelection;
}
function handleEmptySelection(tier, grepPattern, selectedFiles) {
    if (grepPattern && !tier && selectedFiles.length === 0) {
        exitWithError(`No tests matched --grep ${grepPattern}`, 1);
    }
    if (tier && selectedFiles.length === 0) {
        process.stderr.write(`[no files for tier ${tier}]\n`);
        process.exit(0);
    }
}
function main() {
    const { dryRun, grepPattern, manifestMode, manifestPath, runnerArgs, testFiles, tier, } = parseArgs(process.argv.slice(2));
    const rootDir = process.cwd();
    if (shouldSkipTier(tier)) {
        process.stderr.write('[skipped: RUN_EXPENSIVE_TESTS unset]\n');
        process.exit(0);
    }
    const manifestEntries = manifestPath ? readManifestEntries(rootDir, manifestPath) : null;
    const selectedFiles = selectFiles(rootDir, tier, grepPattern, testFiles, manifestEntries, manifestMode);
    handleEmptySelection(tier, grepPattern, selectedFiles);
    if (dryRun) {
        if (selectedFiles.length > 0)
            process.stdout.write(`${selectedFiles.join('\n')}\n`);
        process.exit(0);
    }
    const nodeArgs = ['--test', ...clampTestConcurrency(runnerArgs), ...selectedFiles];
    const result = spawnSync(process.execPath, nodeArgs, {
        stdio: 'inherit',
        timeout: getRunnerTimeoutMs(),
    });
    if (result.error) {
        exitWithError(result.error.message, 1);
    }
    process.exit(result.status ?? 1);
}
main();
