// Planu — Design Analyzer (SPEC-483)
// Rule-based analyzer: SOLID violations, naming smells, complexity hotspots, god objects.
import { readFile, readdir } from 'node:fs/promises';
import { join, relative, extname, basename } from 'node:path';
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
/** Lines above this threshold in a single file → god object candidate. */
const GOD_OBJECT_LINE_THRESHOLD = 400;
/** Number of exported functions above this → low cohesion smell. */
const LOW_COHESION_EXPORT_THRESHOLD = 15;
/** Short abbreviations that signal naming smells (whole-word match). */
const ABBREV_PATTERN = /\b(tmp|idx|cnt|mgr|hdlr|btn|msg|cfg|resp|req|res|err|cb|fn|ptr|buf|len|sz|val|num)\b/;
/** Generic names that add no semantic value. */
const GENERIC_NAME_PATTERN = /\b(data|info|helper|utils|util|misc|common|shared|base|abstract|manager|handler|processor|service|controller)\b/i;
/** Complexity hotspot: deeply nested blocks (4+ levels of indent). */
const DEEP_NESTING_PATTERN = /^(\s{16,}|\t{4,})/;
// ---------------------------------------------------------------------------
// File discovery
// ---------------------------------------------------------------------------
async function collectFiles(root, filterFiles) {
    if (filterFiles !== undefined && filterFiles.length > 0) {
        return filterFiles.map((f) => join(root, f)).filter((f) => isSourceFile(basename(f)));
    }
    return walkDir(root, root);
}
async function walkDir(dir, root) {
    const results = [];
    let entries;
    try {
        entries = await readdir(dir, { withFileTypes: true, encoding: 'utf-8' });
    }
    catch {
        return results;
    }
    for (const entry of entries) {
        if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === 'dist') {
            continue;
        }
        const full = join(dir, entry.name);
        if (entry.isDirectory()) {
            const sub = await walkDir(full, root);
            results.push(...sub);
        }
        else if (isSourceFile(entry.name)) {
            results.push(full);
        }
    }
    return results;
}
function isSourceFile(name) {
    const ext = extname(name);
    return ['.ts', '.js', '.tsx', '.jsx'].includes(ext) && !name.endsWith('.d.ts');
}
// ---------------------------------------------------------------------------
// Per-file analysis
// ---------------------------------------------------------------------------
async function analyzeFile(filePath, root) {
    let content;
    try {
        content = await readFile(filePath, 'utf-8');
    }
    catch {
        return [];
    }
    const rel = relative(root, filePath);
    const lines = content.split('\n');
    const findings = [];
    // God object: file too long → low cohesion risk
    if (lines.length > GOD_OBJECT_LINE_THRESHOLD) {
        findings.push({
            severity: 'high',
            file: rel,
            line: 0,
            ruleId: 'design/god-object',
            message: `File has ${lines.length} lines (>${GOD_OBJECT_LINE_THRESHOLD}). Consider splitting into smaller modules.`,
            fixHint: 'Extract domain-specific logic into separate files under a subdirectory.',
        });
    }
    // Count exports → cohesion proxy
    const exportCount = lines.filter((l) => /^\s*export\s+(function|class|const|interface|type|enum)/.test(l)).length;
    if (exportCount > LOW_COHESION_EXPORT_THRESHOLD) {
        findings.push({
            severity: 'medium',
            file: rel,
            line: 0,
            ruleId: 'design/low-cohesion',
            message: `File exports ${exportCount} symbols (>${LOW_COHESION_EXPORT_THRESHOLD}). Single Responsibility may be violated.`,
            fixHint: 'Group related exports into domain-focused files.',
        });
    }
    // Per-line checks
    lines.forEach((line, idx) => {
        const lineNum = idx + 1;
        // Naming smells: abbreviations in identifiers (variable/param/function names)
        if (ABBREV_PATTERN.test(line) && /\b(const|let|var|function|param|=>)\b/.test(line)) {
            findings.push({
                severity: 'nit',
                file: rel,
                line: lineNum,
                ruleId: 'design/naming-abbreviation',
                message: `Possible naming abbreviation detected: "${ABBREV_PATTERN.exec(line)?.[0] ?? ''}". Prefer descriptive names.`,
                fixHint: 'Replace abbreviated names with full, self-documenting identifiers.',
            });
        }
        // Generic names
        const genericMatch = /\b(?:function|const|class)\s+(\w+)/.exec(line);
        if (genericMatch !== null) {
            const name = genericMatch[1] ?? '';
            if (GENERIC_NAME_PATTERN.test(name) && name.length < 12) {
                findings.push({
                    severity: 'low',
                    file: rel,
                    line: lineNum,
                    ruleId: 'design/generic-name',
                    message: `Generic identifier name "${name}" conveys no domain intent.`,
                    fixHint: 'Choose a name that reflects the specific domain concept.',
                });
            }
        }
        // Deep nesting → complexity hotspot
        if (DEEP_NESTING_PATTERN.test(line) && line.trim().length > 0) {
            findings.push({
                severity: 'medium',
                file: rel,
                line: lineNum,
                ruleId: 'design/deep-nesting',
                message: 'Deeply nested code block detected (4+ levels). Complexity hotspot.',
                fixHint: 'Extract nested logic into named helper functions or use early returns.',
            });
        }
    });
    return findings;
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
export async function runDesignAnalyzer(projectPath, files) {
    const filePaths = await collectFiles(projectPath, files);
    const allFindings = [];
    for (const fp of filePaths) {
        const fileFindings = await analyzeFile(fp, projectPath);
        allFindings.push(...fileFindings);
    }
    return {
        domain: 'design',
        findings: allFindings,
        analyzedAt: new Date().toISOString(),
    };
}
//# sourceMappingURL=design-analyzer.js.map