"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.gatherGenericMetrics = gatherGenericMetrics;
const runner_1 = require("./runner");
const walk_source_files_1 = require("./walk-source-files");
const debug_statements_1 = require("./debug-statements");
const languages_1 = require("../../languages");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
/** Count how many of `relPaths` exist under `root`. Cross-platform
 *  replacement for `ls a b c` existence-count shell-outs (the prior
 *  `ls` form returned 0 on Windows). */
function countExisting(root, relPaths) {
    return relPaths.filter((rel) => fs.existsSync(path.join(root, rel))).length;
}
// D055/D056/D072 (2.4.7): every per-source-file metric (rawSourceFiles,
// wc-l walk, jsConsoleCount, anyTypeCount, evalCount, docCommentFiles,
// tlsDisabledCount, testFiles) routes through walkSourceFiles +
// countLineMatches. Closes the class of "site-specific walker drift"
// (D075/D077) and "grep maxBuffer overflow" (D082/D083/D084) by
// construction: ONE walker, ONE counter, files pruned at the
// directory boundary so we never read minified .min.js into memory.
//
// All file discovery + counting routes through pure-Node helpers
// (walkSourceFiles / countDirectories / countExisting / fs), never a
// POSIX `find`/`ls`/`wc` shell-out — so the metrics compute identically
// on Windows.
/**
 * Resolve the git toplevel for `cwd`. D026 (2.4.7): cross-cutting repo
 * artifacts (`.github/`, `README.md`, `CONTRIBUTING.md`, `Makefile`,
 * `.env.example`, etc.) conventionally live at the repo root, not in
 * the subdirectory a user happens to scan. The .NET WinForms benchmark's baseline F8:
 * customer ran `dxkit health Code/Source/`; both Documentation and DX
 * dimensions returned 0/100 because none of those probes found
 * matches in `Code/Source/`. The fix scopes those probes to the
 * git toplevel.
 *
 * Returns `cwd` unchanged when `git rev-parse --show-toplevel` fails
 * (not in a git repo, or git missing) so non-git workflows keep
 * working — they just don't get the toplevel-scoped improvement.
 */
function resolveGitToplevel(cwd) {
    const top = (0, runner_1.run)('git rev-parse --show-toplevel', cwd).trim();
    return top.length > 0 ? top : cwd;
}
/**
 * Count newlines in `content`. Equivalent to `wc -l` semantics:
 * trailing newline NOT double-counted; file with no trailing newline
 * still counts its last line.
 */
function lineCount(content) {
    if (content.length === 0)
        return 0;
    let count = 0;
    for (let i = 0; i < content.length; i++) {
        if (content.charCodeAt(i) === 10 /* \n */)
            count++;
    }
    // No trailing newline → the last partial line was a line too.
    if (content.charCodeAt(content.length - 1) !== 10)
        count++;
    return count;
}
/**
 * Gather metrics using only built-in Unix tools + the canonical walker.
 *
 * `languageFlags` drives the architecturalShape-aware path counters
 * (primary components, route handlers, data models). When omitted
 * (legacy callers, tests) the path counters degrade to zero rather
 * than silently using a hardcoded backend-centric default — every
 * shipped caller in the codebase threads the active stack through.
 */
function gatherGenericMetrics(cwd, languageFlags) {
    // D026 (2.4.7): repo-level artifacts probed from git toplevel; code-
    // level metrics (source-file counts, hygiene grep, semgrep) stay
    // scoped to `cwd` so analyzing a subdirectory still measures that
    // subdir's code quality.
    const repoRoot = resolveGitToplevel(cwd);
    // ─── Source / test file enumeration ───────────────────────────────────────
    // ONE walker, every consumer routes through it. The walker's filter
    // pipeline already applies: exclusions (.gitignore + .dxkit-ignore +
    // bundled defaults), autogen basename globs (*.designer.cs etc.),
    // autogen header markers (<auto-generated> / @generated / DO NOT EDIT),
    // test-file patterns (per `includeTests`). Memoized per cwd+opts.
    //
    // 2.4.7 class-fix alignment: `sourceFiles` now excludes test files,
    // matching test-gaps' definition. Pre-fix health included tests in
    // `sourceFiles` (D075 same-label-different-meaning class). The
    // Testing dimension's `testRatio = testFiles / sourceFiles` is
    // mathematically equivalent (numerator drops to 0 simultaneously
    // when there's a test file in the denominator), so user-visible
    // scoring is unchanged in practice.
    const sourceList = (0, walk_source_files_1.walkSourceFiles)(cwd);
    const sourceListWithTests = (0, walk_source_files_1.walkSourceFiles)(cwd, { includeTests: true });
    const testList = sourceListWithTests.filter((p) => !sourceList.includes(p));
    // ─── Per-file line counts (replaces find + xargs wc -l) ───────────────────
    // 2.4.7: pure in-process read. Catches the D028-header / autogen
    // filtering at walker-time, so we don't need a second-pass header
    // filter the way the old code did. Also: largestFiles + filesOver500.
    const filteredFiles = [];
    for (const relPath of sourceList) {
        let content;
        try {
            content = fs.readFileSync(path.join(cwd, relPath), 'utf-8');
        }
        catch {
            continue;
        }
        filteredFiles.push({ path: relPath, lines: lineCount(content) });
    }
    let totalLines = 0;
    let largestFileLines = 0;
    let largestFilePath = '';
    let filesOver500Lines = 0;
    for (const f of filteredFiles) {
        totalLines += f.lines;
        if (f.lines > largestFileLines) {
            largestFileLines = f.lines;
            largestFilePath = f.path;
        }
        if (f.lines > 500)
            filesOver500Lines++;
    }
    const sourceFiles = sourceList.length;
    // Every file over the canonical large-file threshold, sorted
    // descending. Filtered (not pre-sliced) at this layer so the
    // baseline `large-file` producer captures one entry per file and
    // the per-kind count matches `filesOver500Lines` above. The
    // markdown renderer caps to top 10 at the display site.
    const largestFiles = filteredFiles.filter((f) => f.lines > 500).sort((a, b) => b.lines - a.lines);
    // ─── Console / debug / type-escape / eval counts (D074 closure) ───────────
    // skipComments: true filters lines beginning with //, /*, or # so
    // commented-out occurrences don't inflate the count.
    //
    // 2.4.7 D079 closure: `consoleLogCount` now routes through the shared
    // `gatherDebugStatements` helper — same implementation in health AND
    // quality, so the two reports cannot drift on this metric. Counts
    // the print family across active languages (TS/JS/Py/Go).
    const consoleLogCount = (0, debug_statements_1.gatherDebugStatements)(cwd).count;
    // TypeScript escape-hatch count.
    const tsFiles = (0, walk_source_files_1.walkSourceFiles)(cwd, {
        extensions: ['.ts', '.tsx', '.js', '.jsx'],
        includeTests: true,
    });
    // prettier-ignore
    const anyTypeCount = (0, walk_source_files_1.countLineMatches)(cwd, tsFiles, [': any'], { skipComments: true }).lines; // slop-ok
    // ─── Documentation ────────────────────────────────────────────────────────
    // D026 (2.4.7): probe from repo root, not from cwd.
    const readmeExists = (0, runner_1.fileExists)(repoRoot, 'README.md', 'readme.md');
    // Newline count matches the prior `wc -l README.md` exactly, read
    // cross-platform via fs rather than a POSIX `wc | awk` pipe.
    let readmeLines = 0;
    try {
        const md = fs.readFileSync(path.join(repoRoot, 'README.md'), 'utf-8');
        readmeLines = (md.match(/\n/g) || []).length;
    }
    catch {
        /* no README.md at repoRoot — leave at 0 */
    }
    // D027 (2.4.7): doc-comment regex + include flags derive from the
    // language pack registry. countLineMatches mode='files' returns the
    // file count. NO skipComments here — we explicitly WANT comment lines.
    const docCommentPatterns = (0, languages_1.allDocCommentPatterns)();
    const docCommentFiles = docCommentPatterns.length > 0
        ? (0, walk_source_files_1.countLineMatches)(cwd, sourceList, docCommentPatterns, { mode: 'files' }).files
        : 0;
    const apiDocsExist = (0, runner_1.fileExists)(repoRoot, 'openapi.json', 'openapi.yaml', 'swagger.json', 'swagger.yaml');
    const architectureDocsExist = (0, runner_1.fileExists)(repoRoot, 'ARCHITECTURE.md', 'docs/', 'ADR/', 'adr/');
    const contributingExists = (0, runner_1.fileExists)(repoRoot, 'CONTRIBUTING.md');
    const changelogExists = (0, runner_1.fileExists)(repoRoot, 'CHANGELOG.md', 'CHANGES.md');
    // ─── Security (eval + TLS-bypass + cert/key files) ────────────────────────
    // D056 (2.4.7): routed through the canonical counter so .dxkit-ignore /
    // .gitignore / bundled defaults all apply. D074 (2.4.7): skipComments
    // strips commented-out `eval(` and TLS-bypass idioms.
    const evalFiles = (0, walk_source_files_1.walkSourceFiles)(cwd, {
        extensions: ['.ts', '.tsx', '.js', '.jsx', '.py'],
        includeTests: true,
    });
    const evalCount = (0, walk_source_files_1.countLineMatches)(cwd, evalFiles, ['\\beval\\('], {
        skipComments: true,
    }).lines;
    const privateKeyFiles = (0, walk_source_files_1.walkSourceFiles)(cwd, {
        extensions: ['.key', '.pem'],
        includeTests: true,
        includeAutogen: true,
    }).length;
    const envFilesInGit = (0, runner_1.countLines)('git ls-files .env .env.*', cwd);
    // D034 (2.4.7): TLS-bypass detection derives from the pack registry.
    // D074 (2.4.7): skipComments closes the "vuln-scan rendered a
    // `// NODE_TLS_REJECT_UNAUTHORIZED=0` comment as a HIGH finding"
    // false-positive class on platform.
    const tlsBypassPatterns = (0, languages_1.allTlsBypassPatterns)();
    const tlsDisabledCount = tlsBypassPatterns.length > 0
        ? (0, walk_source_files_1.countLineMatches)(cwd, sourceList, tlsBypassPatterns, { skipComments: true }).lines
        : 0;
    // ─── Maintainability (per-pack architectural counts) ───────────────────────
    // Primary-component, route-handler, and data-model counts derive
    // from the active language packs' `architecturalShape`. Pre-extension
    // these were three hardcoded find commands keyed on backend-centric
    // paths (`controllers/`, `handlers/`, `views/`, `models/`), so a pure
    // React frontend or a .NET WinForms desktop app reported zero in all
    // three counters regardless of how much primary architecture sat on
    // disk. The walker visit already enumerated every source file; here
    // we filter `sourceList` (pre-walked + autogen-excluded + exclusion-
    // honoured) by case-insensitive substring match against the unioned
    // pack contributions.
    const flags = languageFlags ?? {};
    const primaryPaths = (0, languages_1.allPrimaryComponentPaths)(flags).map((p) => p.toLowerCase());
    const routePaths = (0, languages_1.allRoutePaths)(flags).map((p) => p.toLowerCase());
    const modelPaths = (0, languages_1.allModelPaths)(flags).map((p) => p.toLowerCase());
    const matchesAny = (p, needles) => {
        if (needles.length === 0)
            return false;
        const lower = p.toLowerCase();
        for (const n of needles) {
            if (lower.includes(n))
                return true;
        }
        return false;
    };
    let controllers = 0;
    let routeHandlerFiles = 0;
    let models = 0;
    for (const rel of sourceList) {
        // Match against the path with a leading slash so substring patterns
        // like "/controllers/" anchor on a directory boundary, never on a
        // filename like "controllers-helper.ts" floating outside such a dir.
        const anchored = '/' + rel;
        if (matchesAny(anchored, primaryPaths))
            controllers++;
        if (matchesAny(anchored, routePaths))
            routeHandlerFiles++;
        if (matchesAny(anchored, modelPaths))
            models++;
    }
    const directories = (0, walk_source_files_1.countDirectories)(cwd);
    // ─── Developer Experience (repo-root scoped) ──────────────────────────────
    // Cross-platform existence/count checks via fs, replacing POSIX
    // `find`/`ls` shell-outs (which returned 0 on Windows).
    let workflowFileCount = 0;
    try {
        workflowFileCount = fs
            .readdirSync(path.join(repoRoot, '.github', 'workflows'))
            .filter((n) => n.endsWith('.yml') || n.endsWith('.yaml')).length;
    }
    catch {
        /* no .github/workflows dir */
    }
    const ciConfigCount = workflowFileCount +
        countExisting(repoRoot, ['.gitlab-ci.yml', 'Jenkinsfile', '.circleci/config.yml']);
    const dockerConfigCount = countExisting(repoRoot, [
        'Dockerfile',
        'docker-compose.yml',
        'docker-compose.yaml',
        '.devcontainer/devcontainer.json',
    ]);
    const precommitConfigCount = countExisting(repoRoot, [
        '.husky',
        '.pre-commit-config.yaml',
        '.git/hooks/pre-commit',
    ]);
    const makefileExists = (0, runner_1.fileExists)(repoRoot, 'Makefile', 'justfile', 'Taskfile.yml');
    const envExampleExists = (0, runner_1.fileExists)(repoRoot, '.env.example', '.env.sample', '.env.template');
    const coverageConfigExists = (0, runner_1.fileExists)(repoRoot, '.nycrc', '.nycrc.json', '.c8rc', '.c8rc.json', 'jest.config.js', 'jest.config.ts', 'vitest.config.ts', 'vitest.config.js', '.coveragerc', 'setup.cfg', 'pytest.ini');
    return {
        sourceFiles,
        testFiles: testList.length,
        totalLines,
        coverageConfigExists,
        filesOver500Lines,
        largestFileLines,
        largestFilePath,
        largestFiles,
        consoleLogCount,
        anyTypeCount,
        readmeExists,
        readmeLines,
        docCommentFiles,
        apiDocsExist,
        architectureDocsExist,
        contributingExists,
        changelogExists,
        evalCount,
        privateKeyFiles,
        envFilesInGit,
        tlsDisabledCount,
        controllers,
        models,
        routeHandlerFiles,
        directories,
        ciConfigCount,
        dockerConfigCount,
        precommitConfigCount,
        makefileExists,
        envExampleExists,
        toolsUsed: ['grep', 'find', 'wc', 'git'],
        toolsUnavailable: [],
    };
}
//# sourceMappingURL=generic.js.map