"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DuplicationDetector = void 0;
class DuplicationDetector {
    minLines;
    similarityThreshold;
    constructor(minLines = 5, similarityThreshold = 0.8) {
        this.minLines = minLines;
        this.similarityThreshold = similarityThreshold;
    }
    detectClones(files) {
        const groups = [];
        const fileEntries = Object.entries(files);
        let groupId = 0;
        for (let i = 0; i < fileEntries.length; i++) {
            for (let j = i + 1; j < fileEntries.length; j++) {
                const [fileA, codeA] = fileEntries[i];
                const [fileB, codeB] = fileEntries[j];
                const clones = this.findClonesBetween(codeA, codeB, fileA, fileB);
                for (const clone of clones) {
                    groupId++;
                    groups.push({
                        id: `dup_${groupId}`,
                        files: [clone.fileA, clone.fileB],
                        lines: [clone.lineA, clone.lineB],
                        code: clone.code,
                        similarity: clone.similarity,
                        variations: this.extractVariations(clone.code),
                    });
                }
            }
        }
        return groups;
    }
    groupVariations(groups) {
        const grouped = new Map();
        for (const group of groups) {
            const signature = this.getSignature(group.code);
            if (!grouped.has(signature)) {
                grouped.set(signature, []);
            }
            grouped.get(signature).push(group);
        }
        return grouped;
    }
    generateUnifiedHelper(group) {
        const varName = this.extractVariableName(group.code);
        return `export function ${varName}(input: unknown): unknown {\n  // Unified from ${group.files.length} duplicates\n  ${group.code.trim()}\n}\n`;
    }
    getExtractHelperAction(group) {
        return `Extract helper from ${group.files.length} duplicates across ${group.files.join(', ')}`;
    }
    getShowAllLocationsAction(group) {
        return group.files
            .map((f, i) => `  ${f}:${group.lines[i]} — ${group.code.substring(0, 60)}...`)
            .join('\n');
    }
    getStats(groups) {
        const allFiles = new Set();
        for (const g of groups) {
            for (const f of g.files)
                allFiles.add(f);
        }
        const avgSim = groups.length > 0
            ? Math.round(groups.reduce((a, g) => a + g.similarity, 0) / groups.length * 100) / 100
            : 0;
        return { totalGroups: groups.length, totalFiles: allFiles.size, avgSimilarity: avgSim };
    }
    findClonesBetween(codeA, codeB, fileA, fileB) {
        const clones = [];
        const linesA = codeA.split('\n');
        const linesB = codeB.split('\n');
        for (let i = 0; i <= linesA.length - this.minLines; i++) {
            for (let j = 0; j <= linesB.length - this.minLines; j++) {
                const chunkA = linesA.slice(i, i + this.minLines).join('\n');
                const chunkB = linesB.slice(j, j + this.minLines).join('\n');
                const similarity = this.computeSimilarity(chunkA, chunkB);
                if (similarity >= this.similarityThreshold) {
                    clones.push({
                        fileA,
                        fileB,
                        lineA: i + 1,
                        lineB: j + 1,
                        code: chunkA,
                        similarity,
                    });
                }
            }
        }
        return clones;
    }
    computeSimilarity(a, b) {
        const tokensA = new Set(a.split(/\s+/));
        const tokensB = new Set(b.split(/\s+/));
        const intersection = new Set([...tokensA].filter((t) => tokensB.has(t)));
        const union = new Set([...tokensA, ...tokensB]);
        return intersection.size / Math.max(1, union.size);
    }
    getSignature(code) {
        return code
            .replace(/['"][^'"]*['"]/g, '""')
            .replace(/\b\w+\b/g, (m) => (m.length > 3 ? 'X' : m))
            .substring(0, 50);
    }
    extractVariations(code) {
        const lines = code.split('\n');
        return lines.filter((l) => l.trim().length > 0).slice(0, 3);
    }
    extractVariableName(code) {
        const match = code.match(/(?:^|\n)\s*(?:export\s+)?(?:function|const)\s+(\w+)/m);
        return match ? match[1] : 'unifiedHelper';
    }
}
exports.DuplicationDetector = DuplicationDetector;
//# sourceMappingURL=duplication-detector.js.map