// Planu — Paradigm detection engine (SPEC-035a)
// Analyses keywords, dependencies, and directory structure to detect programming paradigms.
import { glob } from 'glob';
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
import { collectDependencies, IGNORE_DIRS } from './detection-utils.js';
import { getAllParadigms } from './paradigm-registry.js';
// Maximum source files to scan for keywords to avoid large-repo slowness.
const MAX_FILES_TO_SCAN = 10_000;
// Minimum confidence to include a paradigm in the report.
const MIN_CONFIDENCE = 0.05;
// Threshold below which a project is considered "hybrid/pragmatic".
const HYBRID_THRESHOLD = 0.4;
/**
 * Detect programming paradigm signals in a project directory.
 * Pure function — same input always produces same output, no side effects.
 */
export async function detectSignals(projectPath) {
    const [paradigms, deps, dirs, codeKeywords] = await Promise.all([
        getAllParadigms(),
        collectProjectDependencies(projectPath),
        collectDirectories(projectPath),
        collectCodeKeywords(projectPath),
    ]);
    const signals = [];
    for (const entry of paradigms) {
        signals.push(...buildDependencySignals(entry, deps));
        signals.push(...buildDirectorySignals(entry, dirs));
        signals.push(...buildKeywordSignals(entry, codeKeywords));
    }
    return signals;
}
/**
 * Run the full paradigm detection pipeline for a project.
 * Returns a ParadigmReport including dominant paradigm, all scores, and coherence.
 */
export async function detectParadigms(projectPath) {
    const signals = await detectSignals(projectPath);
    const paradigms = await getAllParadigms();
    const scores = computeScores(signals, paradigms);
    const sorted = scores.sort((a, b) => b.confidence - a.confidence);
    const aboveThreshold = sorted.filter((s) => s.confidence >= MIN_CONFIDENCE);
    const dominant = aboveThreshold[0] ?? null;
    const mixPattern = classifyMix(aboveThreshold);
    const coherenceScore = computeCoherence(aboveThreshold);
    const summary = buildSummary(dominant, aboveThreshold, mixPattern);
    return {
        dominant,
        all: aboveThreshold,
        mixPattern,
        coherenceScore,
        summary,
        detectedAt: new Date().toISOString(),
    };
}
// --- Internal helpers ---
async function collectProjectDependencies(projectPath) {
    try {
        return await collectDependencies(projectPath);
    }
    catch {
        return new Set();
    }
}
async function collectDirectories(projectPath) {
    try {
        const entries = await glob('**/', {
            cwd: projectPath,
            ignore: IGNORE_DIRS,
            maxDepth: 4,
        });
        return entries.map((e) => basename(e.replace(/\/$/, '')));
    }
    catch {
        return [];
    }
}
async function collectCodeKeywords(projectPath) {
    const sourceExtensions = [
        '**/*.ts',
        '**/*.tsx',
        '**/*.js',
        '**/*.jsx',
        '**/*.py',
        '**/*.java',
        '**/*.kt',
        '**/*.cs',
        '**/*.go',
        '**/*.rs',
        '**/*.swift',
        '**/*.hs',
        '**/*.elm',
        '**/*.purs',
    ];
    const allKeywords = [];
    try {
        const files = await glob(sourceExtensions, {
            cwd: projectPath,
            ignore: IGNORE_DIRS,
            nodir: true,
            maxDepth: 6,
        });
        const sample = files.slice(0, MAX_FILES_TO_SCAN);
        await Promise.all(sample.map(async (relativePath) => {
            try {
                const content = await readFile(`${projectPath}/${relativePath}`, 'utf-8');
                allKeywords.push(content);
            }
            catch {
                // skip unreadable files
            }
        }));
    }
    catch {
        // skip glob errors
    }
    return allKeywords.join('\n');
}
function buildDependencySignals(entry, deps) {
    const signals = [];
    for (const dep of entry.signals.dependencies ?? []) {
        if (deps.has(dep) || [...deps].some((d) => d.includes(dep))) {
            signals.push({
                paradigmId: entry.id,
                signalType: 'dependency',
                value: dep,
                weight: 0.6,
                location: 'package manifest',
            });
        }
    }
    return signals;
}
function buildDirectorySignals(entry, dirs) {
    const signals = [];
    for (const dir of entry.signals.directories ?? []) {
        const dirBase = basename(dir);
        if (dirs.some((d) => d === dirBase)) {
            signals.push({
                paradigmId: entry.id,
                signalType: 'structure',
                value: dir,
                weight: 0.2,
                location: `directory: ${dir}`,
            });
        }
    }
    return signals;
}
function buildKeywordSignals(entry, code) {
    if (!code) {
        return [];
    }
    const signals = [];
    for (const keyword of entry.signals.keywords ?? []) {
        if (code.includes(keyword)) {
            signals.push({
                paradigmId: entry.id,
                signalType: 'keyword',
                value: keyword,
                weight: 0.1,
                location: 'source files',
            });
        }
    }
    return signals;
}
function computeScores(signals, paradigms) {
    const byParadigm = new Map();
    for (const signal of signals) {
        const existing = byParadigm.get(signal.paradigmId) ?? [];
        existing.push(signal);
        byParadigm.set(signal.paradigmId, existing);
    }
    const scores = [];
    for (const entry of paradigms) {
        const entrySignals = byParadigm.get(entry.id) ?? [];
        const raw = entrySignals.reduce((sum, s) => sum + s.weight, 0);
        // Normalize: clamp to [0, 1] using a soft-cap formula
        const confidence = Math.min(1, raw / 3);
        scores.push({
            paradigmId: entry.id,
            name: entry.name,
            confidence,
            signals: entrySignals,
        });
    }
    return scores;
}
function classifyMix(scores) {
    const [top, second] = scores;
    if (!top || !second) {
        return 'none';
    }
    // Two paradigms are significant when both are above threshold
    if (second.confidence >= HYBRID_THRESHOLD) {
        // Coherent mix: compatible paradigms (e.g. component-based + functional)
        return 'coherent';
    }
    if (scores.filter((s) => s.confidence >= MIN_CONFIDENCE).length >= 3) {
        // Many weak signals without a clear pattern
        return 'inconsistent';
    }
    return 'none';
}
function computeCoherence(scores) {
    if (scores.length <= 1) {
        return scores.length === 0 ? 0 : 1;
    }
    // Higher coherence when dominant paradigm has much higher confidence than others
    const [top, ...rest] = scores;
    /* v8 ignore start */
    const dominance = top?.confidence ?? 0;
    /* v8 ignore stop */
    const avgRest = rest.reduce((a, b) => a + b.confidence, 0) / rest.length;
    return Math.min(1, dominance / Math.max(avgRest, 0.01));
}
function buildSummary(dominant, all, mix) {
    if (!dominant || dominant.confidence < HYBRID_THRESHOLD) {
        return 'No dominant paradigm detected. The project appears to be hybrid/pragmatic.';
    }
    const secondary = all.slice(1, 3).filter((s) => s.confidence >= MIN_CONFIDENCE);
    const base = `Dominant paradigm: ${dominant.name} (confidence: ${(dominant.confidence * 100).toFixed(0)}%).`;
    if (secondary.length === 0) {
        return base;
    }
    const secondaryNames = secondary.map((s) => s.name).join(', ');
    if (mix === 'coherent') {
        return `${base} Mixed with ${secondaryNames} — coherent blend.`;
    }
    if (mix === 'inconsistent') {
        return `${base} Also detected: ${secondaryNames}. Mix appears inconsistent.`;
    }
    return base;
}
//# sourceMappingURL=paradigm-detector.js.map