// Review dimensions for expert PR analysis (SPEC-303)
import { parseDiffFiles } from './review-helpers.js';
import { runDuplication, runComplexity, runDeadCode, runQualityDimensions, } from './quality-dimensions.js';
export { runQualityDimensions };
function changedFilePaths(diff) {
    return diff
        .split('\n')
        .filter((l) => l.startsWith('diff --git'))
        .map((l) => {
        const m = /b\/(.+)$/.exec(l);
        return m?.[1] ?? '';
    })
        .filter(Boolean);
}
// ---------------------------------------------------------------------------
// Architecture dimension
// ---------------------------------------------------------------------------
function runArchitecture(diff) {
    const findings = [];
    const files = parseDiffFiles(diff);
    // Track added line count per file (heuristic for "file > 400 lines")
    const addedLines = {};
    for (const file of files) {
        addedLines[file.path] = (addedLines[file.path] ?? 0) + file.lines.length;
    }
    for (const file of files) {
        const added = addedLines[file.path] ?? 0;
        if (added > 400) {
            findings.push({
                file: file.path,
                severity: 'warning',
                category: 'architecture',
                message: `File adds ${String(added)} lines in this PR (threshold: 400). Consider splitting.`,
                suggestion: 'Break into smaller modules following single-responsibility principle.',
            });
        }
        for (const { lineNo, content } of file.lines) {
            if (file.path.endsWith('.ts') || file.path.endsWith('.tsx')) {
                const importMatch = /^\s*(?:import|export).*from\s+['"](\.[^'"]+)['"]/.exec(content);
                if (importMatch?.[1]) {
                    const importPath = importMatch[1];
                    if (!importPath.endsWith('.js') && !importPath.endsWith('.json')) {
                        findings.push({
                            file: file.path,
                            line: lineNo,
                            severity: 'warning',
                            category: 'architecture',
                            message: `Missing .js extension in import: "${importPath}"`,
                            suggestion: `Add .js extension: "${importPath}.js"`,
                        });
                    }
                }
                // Circular-looking imports (file imports from parent with same name)
                const circularMatch = /^\s*import.*from\s+['"]\.\.\/([^/'"]+)['"]/.exec(content);
                if (circularMatch?.[1]) {
                    const importedModule = circularMatch[1];
                    const currentModule = file.path
                        .replace(/\.[^.]+$/, '')
                        .split('/')
                        .pop() ?? '';
                    if (importedModule === currentModule) {
                        findings.push({
                            file: file.path,
                            line: lineNo,
                            severity: 'warning',
                            category: 'architecture',
                            message: `Potential circular import: module imports from sibling with same name "${importedModule}"`,
                            suggestion: 'Review module boundaries to avoid circular dependencies.',
                        });
                    }
                }
            }
        }
    }
    return findings;
}
// ---------------------------------------------------------------------------
// Security dimension
// ---------------------------------------------------------------------------
const SECURITY_PATTERNS = [
    {
        pattern: /password\s*[:=]\s*['"][^'"]{1,}['"]/i,
        message: 'Hardcoded password detected',
        suggestion: 'Use environment variables for secrets',
    },
    {
        pattern: /api[_-]?key\s*[:=]\s*['"][^'"]{1,}['"]/i,
        message: 'Hardcoded API key detected',
        suggestion: 'Use environment variables for secrets',
    },
    {
        pattern: /Bearer\s+[A-Za-z0-9\-._~+/]{20,}/,
        message: 'Hardcoded Bearer token detected',
        suggestion: 'Load tokens from environment variables or a secrets manager',
    },
    {
        pattern: /['"`]\s*SELECT\s+.+\s*\+\s*[a-zA-Z_$]/i,
        message: 'Possible SQL string concatenation — SQL injection risk',
        suggestion: 'Use parameterized queries or an ORM',
    },
    {
        pattern: /eval\s*\(/,
        message: 'Use of eval() detected',
        suggestion: 'Avoid eval(); use safer alternatives like JSON.parse()',
    },
    {
        pattern: /dangerouslySetInnerHTML/,
        message: 'dangerouslySetInnerHTML usage detected — XSS risk',
        suggestion: 'Sanitize HTML with DOMPurify before using dangerouslySetInnerHTML',
    },
];
function runSecurity(diff) {
    const findings = [];
    const files = parseDiffFiles(diff);
    for (const file of files) {
        for (const { lineNo, content } of file.lines) {
            for (const { pattern, message, suggestion } of SECURITY_PATTERNS) {
                if (pattern.test(content)) {
                    findings.push({
                        file: file.path,
                        line: lineNo,
                        severity: 'critical',
                        category: 'security',
                        message,
                        suggestion,
                    });
                }
            }
        }
    }
    return findings;
}
// ---------------------------------------------------------------------------
// Tests dimension
// ---------------------------------------------------------------------------
function runTests(diff) {
    const findings = [];
    const paths = changedFilePaths(diff);
    const srcFiles = paths.filter((p) => p.startsWith('src/') && p.endsWith('.ts'));
    const testFiles = new Set(paths.filter((p) => p.startsWith('tests/')));
    for (const src of srcFiles) {
        // src/foo/bar.ts → tests/foo/bar.test.ts
        const relative = src.slice('src/'.length).replace(/\.ts$/, '');
        const expected = `tests/${relative}.test.ts`;
        if (!testFiles.has(expected)) {
            findings.push({
                file: src,
                severity: 'warning',
                category: 'tests',
                message: `Source file changed but no corresponding test file found: ${expected}`,
                suggestion: 'Add or update tests to cover the changed code.',
            });
        }
    }
    return findings;
}
// ---------------------------------------------------------------------------
// Conventions dimension
// ---------------------------------------------------------------------------
function runConventions(diff) {
    const findings = [];
    const files = parseDiffFiles(diff);
    for (const file of files) {
        if (!file.path.endsWith('.ts') && !file.path.endsWith('.tsx')) {
            continue;
        }
        for (const { lineNo, content } of file.lines) {
            if (/:\s*any\b/.test(content) && !content.trimStart().startsWith('//')) {
                findings.push({
                    file: file.path,
                    line: lineNo,
                    severity: 'warning',
                    category: 'conventions',
                    message: 'Explicit `any` type used — weakens type safety',
                    suggestion: 'Replace `any` with a specific type, `unknown`, or a generic.',
                });
            }
        }
        // Check .github/ files for conventional commit patterns
        if (file.path.startsWith('.github/')) {
            for (const { lineNo, content } of file.lines) {
                if (/commit\s+message/i.test(content) && !/conventional/i.test(content)) {
                    findings.push({
                        file: file.path,
                        line: lineNo,
                        severity: 'info',
                        category: 'conventions',
                        message: 'Commit message check found without conventional-commits enforcement',
                        suggestion: 'Ensure commitlint with conventional-commits preset is configured.',
                    });
                }
            }
        }
    }
    return findings;
}
// ---------------------------------------------------------------------------
// Spec-drift dimension
// ---------------------------------------------------------------------------
function runSpecDrift(diff, context) {
    if (!context.specId) {
        return [];
    }
    const findings = [];
    const paths = changedFilePaths(diff);
    const specSlug = context.specId.toLowerCase().replace(/spec-\d+-?/i, '');
    const keywords = specSlug.split(/[-_\s]+/).filter((k) => k.length > 2);
    if (keywords.length === 0) {
        return [];
    }
    for (const filePath of paths) {
        const lowerPath = filePath.toLowerCase();
        const matchesSpec = keywords.some((kw) => lowerPath.includes(kw));
        if (!matchesSpec &&
            filePath.startsWith('src/') &&
            !filePath.includes('types/') &&
            !filePath.includes('index')) {
            findings.push({
                file: filePath,
                severity: 'info',
                category: 'spec-drift',
                message: `File may be outside the scope of ${context.specId} (keywords: ${keywords.join(', ')})`,
                suggestion: 'Verify this change is intentional for this spec, or move to a separate PR.',
            });
        }
    }
    return findings;
}
// ---------------------------------------------------------------------------
// Main orchestrator
// ---------------------------------------------------------------------------
export function runAllDimensions(diff, context) {
    const dimensions = [
        ['architecture', () => runArchitecture(diff)],
        ['security', () => runSecurity(diff)],
        ['tests', () => runTests(diff)],
        ['conventions', () => runConventions(diff)],
        ['spec-drift', () => runSpecDrift(diff, context)],
        ['duplication', () => runDuplication(diff)],
        ['complexity', () => runComplexity(diff)],
        ['dead-code', () => runDeadCode(diff)],
    ];
    return Promise.resolve(dimensions.map(([name, fn]) => {
        const start = Date.now();
        const findings = fn();
        return {
            dimension: name,
            findings,
            duration: Date.now() - start,
        };
    }));
}
//# sourceMappingURL=review-dimensions.js.map