"use strict";
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2024-2026 Gracker (Chris)
// This file is part of SmartPerfetto. See LICENSE for details.
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.collectTraceMatrixCandidates = collectTraceMatrixCandidates;
exports.computeTraceMatrixCandidateCoverage = computeTraceMatrixCandidateCoverage;
exports.parseTraceMatrixAuditArgs = parseTraceMatrixAuditArgs;
exports.buildTraceMatrixAuditReport = buildTraceMatrixAuditReport;
exports.determineTraceMatrixAuditExitCode = determineTraceMatrixAuditExitCode;
exports.buildMarkdownAuditReport = buildMarkdownAuditReport;
exports.buildBenchmarkManifest = buildBenchmarkManifest;
const promises_1 = __importDefault(require("fs/promises"));
const os_1 = __importDefault(require("os"));
const path_1 = __importDefault(require("path"));
const benchmarkTraceProcessorRss_1 = require("./benchmarkTraceProcessorRss");
const MIB = 1024 * 1024;
const DEFAULT_MIN_SIZE_BYTES = 100 * MIB;
const TRACE_EXTENSIONS = new Set([
    '.trace',
    '.pftrace',
    '.perfetto-trace',
    '.pb',
    '.protobuf',
]);
const SKIPPED_DIR_NAMES = new Set([
    '.git',
    '.hg',
    '.svn',
    '.claude',
    'node_modules',
    'dist',
    'build',
    'coverage',
]);
function printUsage() {
    console.log('Usage: npx tsx src/scripts/auditTraceProcessorRssMatrix.ts [options]');
    console.log('');
    console.log('Options:');
    console.log('  --scan-dir <path>             Directory or file to scan. Repeatable.');
    console.log('  --output <path>               JSON audit report path.');
    console.log('  --markdown <path>             Optional Markdown audit report path.');
    console.log('  --benchmark-manifest <path>   Write benchmark manifest when every matrix cell has a candidate.');
    console.log('  --min-size-mb <mb>            Minimum candidate size. Default: 100.');
    console.log('  --exclude-unknown-scene       Hide large traces whose scene cannot be inferred from filename.');
    console.log('  --require-complete-matrix     Exit non-zero if any §0.4.3 scene/size cell lacks a candidate.');
    console.log('  --help                        Show this help.');
}
function isTraceFile(filePath) {
    return TRACE_EXTENSIONS.has(path_1.default.extname(filePath).toLowerCase());
}
async function maybeCreateCandidate(filePath, options) {
    if (!isTraceFile(filePath))
        return null;
    const stat = await promises_1.default.stat(filePath);
    if (!stat.isFile() || stat.size < options.minSizeBytes)
        return null;
    const scene = (0, benchmarkTraceProcessorRss_1.inferBenchmarkSceneFromPath)(filePath);
    if (scene === 'unknown' && !options.includeUnknownScene)
        return null;
    return {
        scene,
        label: path_1.default.basename(filePath),
        path: filePath,
        sizeBytes: stat.size,
        sizeBucket: (0, benchmarkTraceProcessorRss_1.classifyRequiredSizeBucket)(stat.size),
    };
}
async function scanPath(root, options, candidates) {
    let stat;
    try {
        stat = await promises_1.default.lstat(root);
    }
    catch {
        return;
    }
    if (stat.isSymbolicLink())
        return;
    if (stat.isFile()) {
        const candidate = await maybeCreateCandidate(root, options);
        if (candidate)
            candidates.push(candidate);
        return;
    }
    if (!stat.isDirectory())
        return;
    const entries = await promises_1.default.readdir(root, { withFileTypes: true });
    for (const entry of entries) {
        if (entry.isDirectory() && SKIPPED_DIR_NAMES.has(entry.name))
            continue;
        await scanPath(path_1.default.join(root, entry.name), options, candidates);
    }
}
function sortCandidates(candidates) {
    const sceneOrder = new Map(benchmarkTraceProcessorRss_1.REQUIRED_RSS_BENCHMARK_SCENES.map((scene, index) => [scene, index]));
    const bucketOrder = new Map(benchmarkTraceProcessorRss_1.REQUIRED_RSS_BENCHMARK_SIZE_BUCKETS.map((bucket, index) => [bucket, index]));
    return [...candidates].sort((left, right) => {
        const leftScene = sceneOrder.get(left.scene) ?? Number.MAX_SAFE_INTEGER;
        const rightScene = sceneOrder.get(right.scene) ?? Number.MAX_SAFE_INTEGER;
        if (leftScene !== rightScene)
            return leftScene - rightScene;
        const leftBucket = bucketOrder.get(left.sizeBucket) ?? Number.MAX_SAFE_INTEGER;
        const rightBucket = bucketOrder.get(right.sizeBucket) ?? Number.MAX_SAFE_INTEGER;
        if (leftBucket !== rightBucket)
            return leftBucket - rightBucket;
        if (left.sizeBytes !== right.sizeBytes)
            return right.sizeBytes - left.sizeBytes;
        return left.path.localeCompare(right.path);
    });
}
async function collectTraceMatrixCandidates(roots, options = {}) {
    const scanOptions = {
        minSizeBytes: options.minSizeBytes ?? DEFAULT_MIN_SIZE_BYTES,
        includeUnknownScene: options.includeUnknownScene ?? true,
    };
    const candidates = [];
    for (const root of roots) {
        await scanPath(path_1.default.resolve(root), scanOptions, candidates);
    }
    const byPath = new Map();
    for (const candidate of candidates) {
        byPath.set(candidate.path, candidate);
    }
    return sortCandidates(Array.from(byPath.values()));
}
function computeTraceMatrixCandidateCoverage(candidates) {
    const observed = new Set();
    for (const candidate of candidates) {
        if (!benchmarkTraceProcessorRss_1.REQUIRED_RSS_BENCHMARK_SCENES.includes(candidate.scene))
            continue;
        if (!benchmarkTraceProcessorRss_1.REQUIRED_RSS_BENCHMARK_SIZE_BUCKETS.includes(candidate.sizeBucket))
            continue;
        observed.add(`${candidate.scene}:${candidate.sizeBucket}`);
    }
    const missingCells = [];
    for (const scene of benchmarkTraceProcessorRss_1.REQUIRED_RSS_BENCHMARK_SCENES) {
        for (const sizeBucket of benchmarkTraceProcessorRss_1.REQUIRED_RSS_BENCHMARK_SIZE_BUCKETS) {
            const cell = `${scene}:${sizeBucket}`;
            if (!observed.has(cell))
                missingCells.push(cell);
        }
    }
    return {
        complete: missingCells.length === 0,
        missingCells,
        observedCells: Array.from(observed).sort(),
    };
}
function parseTraceMatrixAuditArgs(argv, cwd = process.cwd()) {
    const roots = [];
    let outputPath;
    let markdownPath;
    let benchmarkManifestPath;
    let minSizeBytes = DEFAULT_MIN_SIZE_BYTES;
    let includeUnknownScene = true;
    let requireCompleteMatrix = false;
    for (let i = 0; i < argv.length; i += 1) {
        const arg = argv[i];
        const next = argv[i + 1];
        if (arg === '--help') {
            printUsage();
            process.exit(0);
        }
        if (arg === '--scan-dir') {
            if (!next)
                throw new Error('--scan-dir requires a value');
            roots.push(path_1.default.resolve(cwd, next));
            i += 1;
            continue;
        }
        if (arg === '--output') {
            if (!next)
                throw new Error('--output requires a value');
            outputPath = path_1.default.resolve(cwd, next);
            i += 1;
            continue;
        }
        if (arg === '--markdown') {
            if (!next)
                throw new Error('--markdown requires a value');
            markdownPath = path_1.default.resolve(cwd, next);
            i += 1;
            continue;
        }
        if (arg === '--benchmark-manifest') {
            if (!next)
                throw new Error('--benchmark-manifest requires a value');
            benchmarkManifestPath = path_1.default.resolve(cwd, next);
            i += 1;
            continue;
        }
        if (arg === '--min-size-mb') {
            if (!next)
                throw new Error('--min-size-mb requires a value');
            const minSizeMb = Number.parseInt(next, 10);
            if (!Number.isInteger(minSizeMb) || minSizeMb < 0) {
                throw new Error('--min-size-mb must be an integer >= 0');
            }
            minSizeBytes = minSizeMb * MIB;
            i += 1;
            continue;
        }
        if (arg === '--exclude-unknown-scene') {
            includeUnknownScene = false;
            continue;
        }
        if (arg === '--require-complete-matrix') {
            requireCompleteMatrix = true;
            continue;
        }
        throw new Error(`Unknown argument: ${arg}`);
    }
    return {
        roots,
        minSizeBytes,
        outputPath,
        markdownPath,
        benchmarkManifestPath,
        includeUnknownScene,
        requireCompleteMatrix,
    };
}
async function buildTraceMatrixAuditReport(options) {
    if (options.roots.length === 0) {
        throw new Error('No scan roots provided. Pass --scan-dir at least once.');
    }
    const candidates = await collectTraceMatrixCandidates(options.roots, {
        minSizeBytes: options.minSizeBytes,
        includeUnknownScene: options.includeUnknownScene,
    });
    return {
        generatedAt: new Date().toISOString(),
        roots: options.roots,
        minSizeBytes: options.minSizeBytes,
        host: {
            platform: os_1.default.platform(),
            arch: os_1.default.arch(),
            node: process.version,
        },
        requiredMatrix: {
            scenes: benchmarkTraceProcessorRss_1.REQUIRED_RSS_BENCHMARK_SCENES,
            sizeBuckets: benchmarkTraceProcessorRss_1.REQUIRED_RSS_BENCHMARK_SIZE_BUCKETS,
        },
        coverage: computeTraceMatrixCandidateCoverage(candidates),
        candidates,
    };
}
function determineTraceMatrixAuditExitCode(report, options) {
    if (options.requireCompleteMatrix && !report.coverage.complete)
        return 2;
    return 0;
}
function formatBytes(value) {
    if (value >= 1024 * MIB)
        return `${(value / (1024 * MIB)).toFixed(2)} GiB`;
    return `${(value / MIB).toFixed(1)} MiB`;
}
function buildMarkdownAuditReport(report) {
    const lines = [];
    lines.push('# Trace Processor RSS Matrix Candidate Audit');
    lines.push('');
    lines.push(`Generated: ${report.generatedAt}`);
    lines.push('');
    lines.push('This audit only checks candidate trace availability. It is not RSS benchmark evidence.');
    lines.push('');
    lines.push(`Coverage status: ${report.coverage.complete ? 'candidate_matrix_ready' : 'blocked_missing_required_traces'}`);
    lines.push('');
    lines.push('Scan roots:');
    lines.push('');
    for (const root of report.roots) {
        lines.push(`- ${root}`);
    }
    lines.push('');
    lines.push(`Minimum candidate size: ${formatBytes(report.minSizeBytes)}`);
    lines.push('');
    lines.push('| Trace | Scene | Size bucket | File size | Path |');
    lines.push('| --- | --- | --- | ---: | --- |');
    if (report.candidates.length === 0) {
        lines.push('| none | n/a | n/a | n/a | n/a |');
    }
    else {
        for (const candidate of report.candidates) {
            lines.push([
                `| ${candidate.label}`,
                candidate.scene,
                candidate.sizeBucket,
                formatBytes(candidate.sizeBytes),
                `${candidate.path} |`,
            ].join(' | '));
        }
    }
    lines.push('');
    lines.push('Observed candidate matrix cells:');
    lines.push('');
    if (report.coverage.observedCells.length === 0) {
        lines.push('- none');
    }
    else {
        for (const cell of report.coverage.observedCells) {
            lines.push(`- ${cell}`);
        }
    }
    lines.push('');
    lines.push('Missing required matrix cells:');
    lines.push('');
    if (report.coverage.missingCells.length === 0) {
        lines.push('- none');
    }
    else {
        for (const cell of report.coverage.missingCells) {
            lines.push(`- ${cell}`);
        }
    }
    lines.push('');
    return `${lines.join('\n')}\n`;
}
function buildBenchmarkManifest(report) {
    if (!report.coverage.complete) {
        throw new Error(`Cannot write benchmark manifest; missing cells: ${report.coverage.missingCells.join(', ')}`);
    }
    const selected = new Map();
    for (const candidate of report.candidates) {
        if (!benchmarkTraceProcessorRss_1.REQUIRED_RSS_BENCHMARK_SCENES.includes(candidate.scene))
            continue;
        if (!benchmarkTraceProcessorRss_1.REQUIRED_RSS_BENCHMARK_SIZE_BUCKETS.includes(candidate.sizeBucket))
            continue;
        const key = `${candidate.scene}:${candidate.sizeBucket}`;
        const existing = selected.get(key);
        if (!existing || candidate.sizeBytes > existing.sizeBytes) {
            selected.set(key, candidate);
        }
    }
    const traces = [];
    for (const scene of benchmarkTraceProcessorRss_1.REQUIRED_RSS_BENCHMARK_SCENES) {
        for (const sizeBucket of benchmarkTraceProcessorRss_1.REQUIRED_RSS_BENCHMARK_SIZE_BUCKETS) {
            const candidate = selected.get(`${scene}:${sizeBucket}`);
            if (!candidate) {
                throw new Error(`Cannot write benchmark manifest; missing cell: ${scene}:${sizeBucket}`);
            }
            traces.push({
                scene,
                label: candidate.label,
                path: candidate.path,
                sizeBucket,
            });
        }
    }
    return { traces };
}
async function writeAuditReport(report, options) {
    const outputPath = options.outputPath
        ?? path_1.default.resolve(process.cwd(), 'test-output/trace-processor-rss-matrix-audit.json');
    await promises_1.default.mkdir(path_1.default.dirname(outputPath), { recursive: true });
    await promises_1.default.writeFile(outputPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
    console.log(`[RSS Matrix Audit] JSON report: ${outputPath}`);
    if (options.markdownPath) {
        await promises_1.default.mkdir(path_1.default.dirname(options.markdownPath), { recursive: true });
        await promises_1.default.writeFile(options.markdownPath, buildMarkdownAuditReport(report), 'utf8');
        console.log(`[RSS Matrix Audit] Markdown report: ${options.markdownPath}`);
    }
    if (options.benchmarkManifestPath) {
        if (!report.coverage.complete) {
            console.warn(`[RSS Matrix Audit] Benchmark manifest skipped; missing required candidates: ${report.coverage.missingCells.join(', ')}`);
            return;
        }
        const manifest = buildBenchmarkManifest(report);
        await promises_1.default.mkdir(path_1.default.dirname(options.benchmarkManifestPath), { recursive: true });
        await promises_1.default.writeFile(options.benchmarkManifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
        console.log(`[RSS Matrix Audit] Benchmark manifest: ${options.benchmarkManifestPath}`);
    }
}
async function main() {
    const options = parseTraceMatrixAuditArgs(process.argv.slice(2));
    const report = await buildTraceMatrixAuditReport(options);
    await writeAuditReport(report, options);
    if (!report.coverage.complete) {
        console.warn(`[RSS Matrix Audit] Missing required §0.4.3 candidates: ${report.coverage.missingCells.join(', ')}`);
    }
    process.exitCode = determineTraceMatrixAuditExitCode(report, options) || undefined;
}
if (require.main === module) {
    main().catch((error) => {
        console.error(`[RSS Matrix Audit] ${error.message}`);
        process.exit(1);
    });
}
//# sourceMappingURL=auditTraceProcessorRssMatrix.js.map