// ---------------------------------------------------------------------------
// TF-IDF tokenizer
// ---------------------------------------------------------------------------
const STOP_WORDS = new Set([
    'the',
    'a',
    'an',
    'and',
    'or',
    'but',
    'in',
    'on',
    'at',
    'to',
    'for',
    'of',
    'with',
    'by',
    'from',
    'is',
    'was',
    'are',
    'were',
    'be',
    'been',
    'that',
    'this',
    'it',
    'its',
    'we',
    'i',
    'you',
    'he',
    'she',
    'they',
    'not',
    'no',
    'do',
    'did',
    'does',
    'have',
    'had',
    'has',
    'will',
    'would',
    'can',
    'could',
    'should',
    'may',
    'might',
    'as',
    'so',
    'if',
    'when',
]);
function tokenize(text) {
    return text
        .toLowerCase()
        .replace(/[^a-z0-9\s]/g, ' ')
        .split(/\s+/)
        .filter((w) => w.length > 2 && !STOP_WORDS.has(w));
}
function termFrequency(tokens) {
    const freq = new Map();
    for (const t of tokens) {
        freq.set(t, (freq.get(t) ?? 0) + 1);
    }
    return freq;
}
function overlapScore(queryTerms, docTerms) {
    if (queryTerms.length === 0 || docTerms.length === 0) {
        return 0;
    }
    const docSet = new Set(docTerms);
    const matched = queryTerms.filter((t) => docSet.has(t)).length;
    return matched / queryTerms.length;
}
// ---------------------------------------------------------------------------
// Semantic decision search (TF-IDF based)
// ---------------------------------------------------------------------------
/** Search decisions by relevance to a query string. */
export function searchDecisions(decisions, query, limit = 5) {
    const queryTerms = tokenize(query);
    if (queryTerms.length === 0) {
        return [];
    }
    const scored = [];
    for (const d of decisions) {
        const docText = `${d.what} ${d.why} ${d.alternatives.join(' ')}`;
        const docTerms = tokenize(docText);
        const freqMap = termFrequency(docTerms);
        // Simple relevance: weighted term overlap
        let score = 0;
        for (const qt of queryTerms) {
            const tf = freqMap.get(qt) ?? 0;
            score += tf;
        }
        if (score === 0) {
            continue;
        }
        score = score / Math.max(docTerms.length, 1);
        scored.push({
            specId: d.specId ?? 'global',
            title: d.what,
            content: d.why,
            relevanceScore: score,
        });
    }
    return scored.sort((a, b) => b.relevanceScore - a.relevanceScore).slice(0, limit);
}
// ---------------------------------------------------------------------------
// Similar problems finder
// ---------------------------------------------------------------------------
/** Find specs with similar keywords in title and description. */
export function findSimilarProblems(specs, query, limit = 5) {
    const queryTerms = tokenize(query);
    if (queryTerms.length === 0) {
        return [];
    }
    const scored = [];
    for (const spec of specs) {
        const docText = `${spec.title} ${spec.slug} ${spec.tags.join(' ')}`;
        const docTerms = tokenize(docText);
        const score = overlapScore(queryTerms, docTerms);
        if (score === 0) {
            continue;
        }
        const matchedTerms = queryTerms.filter((t) => docTerms.includes(t));
        scored.push({
            specId: spec.id,
            title: spec.title,
            overlapScore: score,
            matchedTerms,
        });
    }
    return scored.sort((a, b) => b.overlapScore - a.overlapScore).slice(0, limit);
}
// ---------------------------------------------------------------------------
// Lesson extraction from text
// ---------------------------------------------------------------------------
/** Patterns that indicate a lesson in free-form text. */
const LESSON_PATTERNS = [
    { pattern: /aprendimos que[^.!?]*/gi, category: 'process' },
    { pattern: /el problema fue[^.!?]*/gi, category: 'architecture' },
    { pattern: /deber[ií]amos[^.!?]*/gi, category: 'process' },
    { pattern: /we learned that[^.!?]*/gi, category: 'process' },
    { pattern: /the problem was[^.!?]*/gi, category: 'architecture' },
    { pattern: /we should[^.!?]*/gi, category: 'process' },
    { pattern: /always [a-z][^.!?]*/gi, category: 'process' },
    { pattern: /never [a-z][^.!?]*/gi, category: 'process' },
    { pattern: /evitar[^.!?]*/gi, category: 'process' },
    { pattern: /avoid[^.!?]*/gi, category: 'process' },
    { pattern: /siempre [a-z][^.!?]*/gi, category: 'process' },
    { pattern: /nunca [a-z][^.!?]*/gi, category: 'process' },
    { pattern: /make sure[^.!?]*/gi, category: 'process' },
    { pattern: /ensure that[^.!?]*/gi, category: 'process' },
];
/** Extract lesson-like phrases from free-form text (retrospective, post-mortem). */
export function extractLessonsFromText(text) {
    const lessons = [];
    const seen = new Set();
    for (const { pattern, category } of LESSON_PATTERNS) {
        const matches = text.match(pattern) ?? [];
        for (const match of matches) {
            const cleaned = match.trim().replace(/\s+/g, ' ');
            if (cleaned.length < 10) {
                continue;
            }
            const key = cleaned.toLowerCase().slice(0, 50);
            if (seen.has(key)) {
                continue;
            }
            seen.add(key);
            lessons.push({ text: cleaned, category });
        }
    }
    return lessons;
}
// ---------------------------------------------------------------------------
// Knowledge gap detector
// ---------------------------------------------------------------------------
/** List all unique lesson categories present. */
function getLessonCategories(lessons) {
    return new Set(lessons.map((l) => l.category));
}
/** Detect specs without decisions and lesson category gaps. */
export function detectKnowledgeGaps(specs, decisions, lessons) {
    const gaps = [];
    // Specs without any associated decisions
    const specsWithDecisions = new Set(decisions
        .filter((d) => Boolean(d.specId))
        .map((d) => d.specId));
    for (const spec of specs) {
        if (spec.status === 'done' && !specsWithDecisions.has(spec.id)) {
            gaps.push({
                type: 'spec-without-decisions',
                specId: spec.id,
                description: `Spec "${spec.title}" (${spec.id}) has no documented decisions.`,
            });
        }
    }
    // Categories with no lessons
    const LESSON_CATEGORIES = [
        'estimation',
        'testing',
        'architecture',
        'security',
        'performance',
        'ux',
        'process',
    ];
    const existingCategories = getLessonCategories(lessons);
    for (const cat of LESSON_CATEGORIES) {
        if (!existingCategories.has(cat)) {
            gaps.push({
                type: 'category-without-lessons',
                category: cat,
                description: `No lessons documented for category: "${cat}".`,
            });
        }
    }
    return gaps;
}
// ---------------------------------------------------------------------------
// Knowledge summary
// ---------------------------------------------------------------------------
/** Snapshot of project knowledge. */
export function buildKnowledgeSummary(specs, decisions, lessons) {
    const specsWithDecisions = new Set(decisions
        .filter((d) => Boolean(d.specId))
        .map((d) => d.specId));
    const catCounts = new Map();
    for (const l of lessons) {
        catCounts.set(l.category, (catCounts.get(l.category) ?? 0) + 1);
    }
    const topCategories = [...catCounts.entries()]
        .sort((a, b) => b[1] - a[1])
        .slice(0, 5)
        .map(([cat]) => cat);
    const gaps = detectKnowledgeGaps(specs, decisions, lessons);
    return {
        totalDecisions: decisions.length,
        totalLessons: lessons.length,
        totalSpecs: specs.length,
        specsWithDecisions: specsWithDecisions.size,
        topCategories,
        gaps,
    };
}
//# sourceMappingURL=knowledge-base.js.map