// engine/github/quality-dimensions.ts — Quality analysis dimensions (SPEC-309)
// Duplication, complexity, and dead-code checks extracted from review-dimensions.ts
import { parseDiffFiles } from './review-helpers.js';
// ---------------------------------------------------------------------------
// Duplication dimension
// ---------------------------------------------------------------------------
function buildBlockKey(lines) {
    return lines.join('\n');
}
function runDuplication(diff) {
    const findings = [];
    const files = parseDiffFiles(diff);
    for (const file of files) {
        const contents = file.lines.map((l) => l.content);
        const BLOCK_SIZE = 5;
        if (contents.length < BLOCK_SIZE) {
            continue;
        }
        const seen = new Map(); // key -> first lineNo
        const reported = new Set();
        for (let i = 0; i <= contents.length - BLOCK_SIZE; i++) {
            const block = contents.slice(i, i + BLOCK_SIZE);
            // Skip blocks that are all whitespace/empty
            if (block.every((l) => l.trim() === '')) {
                continue;
            }
            const key = buildBlockKey(block);
            if (seen.has(key)) {
                if (!reported.has(key)) {
                    reported.add(key);
                    findings.push({
                        file: file.path,
                        line: file.lines[i]?.lineNo,
                        severity: 'warning',
                        category: 'duplication',
                        message: 'Duplicate code block detected — consider extracting shared logic',
                        suggestion: 'Extract the repeated block into a shared helper function or module.',
                    });
                }
            }
            else {
                seen.set(key, file.lines[i]?.lineNo ?? i);
            }
        }
    }
    return findings;
}
// ---------------------------------------------------------------------------
// Complexity dimension
// ---------------------------------------------------------------------------
function countLeadingIndent(line) {
    let count = 0;
    for (const ch of line) {
        if (ch === ' ') {
            count++;
        }
        else if (ch === '\t') {
            count += 2;
        }
        else {
            break;
        }
    }
    return count;
}
function runComplexity(diff) {
    const findings = [];
    const files = parseDiffFiles(diff);
    for (const file of files) {
        const funcStarts = [];
        for (let i = 0; i < file.lines.length; i++) {
            const lineEntry = file.lines[i];
            if (lineEntry === undefined) {
                continue;
            }
            const { lineNo, content } = lineEntry;
            if (/function\s+\w+\s*\([^)]{60,}\)/.test(content) ||
                /\([^)]{60,}\)\s*[:{=>]/.test(content)) {
                findings.push({
                    file: file.path,
                    line: lineNo,
                    severity: 'critical',
                    category: 'complexity',
                    message: 'Function has too many parameters (>5) — reduces readability and testability',
                    suggestion: 'Group related parameters into an options object.',
                });
            }
            if (/\bfunction\b/.test(content) ||
                /\)\s*(?::\s*\S+\s*)?\{/.test(content) ||
                /=>\s*\{/.test(content)) {
                funcStarts.push({ lineIdx: i, lineNo, indent: countLeadingIndent(content) });
            }
            if (/^\s*(if\s*\(|while\s*\()/.test(content)) {
                const indent = countLeadingIndent(content);
                if (indent > 6) {
                    findings.push({
                        file: file.path,
                        line: lineNo,
                        severity: 'warning',
                        category: 'complexity',
                        message: 'Nested conditional exceeds 3 levels — hard to follow control flow',
                        suggestion: 'Extract inner logic into separate functions or use early returns.',
                    });
                }
            }
        }
        for (let fi = 0; fi < funcStarts.length; fi++) {
            const start = funcStarts[fi];
            if (start === undefined) {
                continue;
            }
            const nextStart = funcStarts[fi + 1];
            const endIdx = nextStart ? nextStart.lineIdx : file.lines.length;
            const funcLen = endIdx - start.lineIdx;
            if (funcLen > 80) {
                findings.push({
                    file: file.path,
                    line: start.lineNo,
                    severity: 'warning',
                    category: 'complexity',
                    message: `Function is ${String(funcLen)} lines long — exceeds 80-line limit`,
                    suggestion: 'Break this function into smaller, focused helpers.',
                });
            }
        }
    }
    return findings;
}
// ---------------------------------------------------------------------------
// Dead-code dimension
// ---------------------------------------------------------------------------
function findUnreachableAfterReturn(lines, startIdx, returnIndent, filePath) {
    for (let j = startIdx + 1; j < lines.length; j++) {
        const next = lines[j];
        if (next === undefined) {
            break;
        }
        if (next.content.trim() === '') {
            continue;
        }
        const nextTrimmed = next.content.trim();
        const nextIndent = countLeadingIndent(next.content);
        if (nextIndent < returnIndent || nextTrimmed.startsWith('}')) {
            break;
        }
        if (nextIndent === returnIndent) {
            return {
                file: filePath,
                line: next.lineNo,
                severity: 'critical',
                category: 'dead-code',
                message: 'Unreachable code after return/throw statement',
                suggestion: 'Remove or relocate the unreachable code.',
            };
        }
        break;
    }
    return undefined;
}
function runDeadCode(diff) {
    const findings = [];
    const files = parseDiffFiles(diff);
    for (const file of files) {
        const lines = file.lines;
        for (let i = 0; i < lines.length; i++) {
            const lineEntry = lines[i];
            if (lineEntry === undefined) {
                continue;
            }
            const { lineNo, content } = lineEntry;
            const trimmed = content.trim();
            if (/\bif\s*\(\s*(?:false|0)\s*\)/.test(content)) {
                findings.push({
                    file: file.path,
                    line: lineNo,
                    severity: 'critical',
                    category: 'dead-code',
                    message: 'Dead code: condition is always false — block is never executed',
                    suggestion: 'Remove the unreachable block.',
                });
                continue;
            }
            if (/^\s*(?:return|throw)\b/.test(content) && !trimmed.endsWith(',')) {
                const returnIndent = countLeadingIndent(content);
                const unreachable = findUnreachableAfterReturn(lines, i, returnIndent, file.path);
                if (unreachable !== undefined) {
                    findings.push(unreachable);
                }
            }
        }
        if (file.path.endsWith('.ts') || file.path.endsWith('.tsx')) {
            const allContent = lines.map((l) => l.content).join('\n');
            for (const { lineNo, content } of lines) {
                const importMatch = /^\s*import\s*\{([^}]+)\}\s*from/.exec(content);
                if (!importMatch?.[1]) {
                    continue;
                }
                const identifiers = importMatch[1]
                    .split(',')
                    .map((s) => s.trim().replace(/\s+as\s+\S+/, ''));
                for (const id of identifiers) {
                    if (!id) {
                        continue;
                    }
                    const withoutImport = allContent.replace(content, '');
                    const escapedId = RegExp.escape(id);
                    const occurrences = withoutImport.split(new RegExp(`\\b${escapedId}\\b`)).length - 1;
                    if (occurrences === 0) {
                        findings.push({
                            file: file.path,
                            line: lineNo,
                            severity: 'warning',
                            category: 'dead-code',
                            message: `Imported identifier "${id}" is never used in this diff hunk`,
                            suggestion: `Remove unused import "${id}" or use it.`,
                        });
                    }
                }
            }
        }
    }
    return findings;
}
// ---------------------------------------------------------------------------
// Quality-only orchestrator (duplication + complexity + dead-code)
// ---------------------------------------------------------------------------
export function runQualityDimensions(diff) {
    const dimensions = [
        ['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,
        };
    }));
}
export { runDuplication, runComplexity, runDeadCode };
//# sourceMappingURL=quality-dimensions.js.map