import { detectLanguage } from '../i18n/index.js';
import { resolvePronounsInText } from './nli-context.js';
import { TOOL_KEYWORDS, ENTITY_SYNONYMS, FRAMEWORK_KEYWORDS } from './nli-keywords.js';
// ---------------------------------------------------------------------------
// Fuzzy string matching (Levenshtein — no external deps)
// ---------------------------------------------------------------------------
/** Compute Levenshtein edit distance between two strings. */
function levenshtein(a, b) {
    const rows = a.length + 1;
    const cols = b.length + 1;
    const dp = Array.from({ length: rows * cols }, () => 0);
    for (let i = 0; i < rows; i++) {
        dp[i * cols] = i;
    }
    for (let j = 0; j < cols; j++) {
        dp[j] = j;
    }
    for (let i = 1; i < rows; i++) {
        for (let j = 1; j < cols; j++) {
            const cost = a[i - 1] === b[j - 1] ? 0 : 1;
            /* v8 ignore start — dp fully initialised, nullish fallback never triggered */
            const del = (dp[(i - 1) * cols + j] ?? 0) + 1;
            const ins = (dp[i * cols + (j - 1)] ?? 0) + 1;
            const sub = (dp[(i - 1) * cols + (j - 1)] ?? 0) + cost;
            /* v8 ignore stop */
            dp[i * cols + j] = Math.min(del, ins, sub);
        }
    }
    /* v8 ignore next — dp fully initialised, fallback never triggered */
    return dp[(rows - 1) * cols + (cols - 1)] ?? 0;
}
/** Compute similarity in [0, 1] between two strings using Levenshtein. */
function similarity(a, b) {
    /* v8 ignore start — caller (matchUserPatterns) already handles a===b via exact-match check */
    if (a === b) {
        return 1;
    }
    /* v8 ignore stop */
    const maxLen = Math.max(a.length, b.length);
    /* v8 ignore start — unreachable: a===b catches both-empty case above */
    if (maxLen === 0) {
        return 1;
    }
    /* v8 ignore stop */
    return 1 - levenshtein(a, b) / maxLen;
}
// ---------------------------------------------------------------------------
// Pattern matching
// ---------------------------------------------------------------------------
/**
 * Match input against learned user patterns.
 * Returns matches sorted by confidence (highest first).
 */
export function matchUserPatterns(input, patterns) {
    const norm = input.toLowerCase().trim();
    const matches = [];
    for (const pattern of patterns) {
        const phraseNorm = pattern.phrase.toLowerCase().trim();
        if (norm === phraseNorm) {
            matches.push({ pattern, confidence: 1.0, source: 'exact' });
            continue;
        }
        if (norm.includes(phraseNorm) || phraseNorm.includes(norm)) {
            matches.push({ pattern, confidence: 0.85, source: 'learned' });
            continue;
        }
        const sim = similarity(norm, phraseNorm);
        if (sim >= 0.7) {
            matches.push({ pattern, confidence: sim * 0.9, source: 'fuzzy' });
        }
    }
    return matches.sort((a, b) => b.confidence - a.confidence);
}
// ---------------------------------------------------------------------------
// Intent extraction
// ---------------------------------------------------------------------------
/** Extract SPEC-XXX identifiers from input text. */
function extractSpecIds(input) {
    const matches = input.toUpperCase().match(/SPEC-\d{3}[-\w]*/g) ?? [];
    return [...new Set(matches)];
}
/** Resolve entity synonyms to canonical tool or entity type. */
function resolveEntitySynonyms(input) {
    const lower = input.toLowerCase();
    for (const [synonym, canonical] of Object.entries(ENTITY_SYNONYMS)) {
        if (lower.includes(synonym)) {
            return lower.replace(synonym, canonical);
        }
    }
    return input;
}
/**
 * Score the input against TOOL_KEYWORDS, returning the best-matching tool
 * and a confidence score in [0, 1].
 */
function scoreTools(input) {
    const lower = input.toLowerCase();
    const results = [];
    for (const [tool, keywords] of Object.entries(TOOL_KEYWORDS)) {
        let score = 0;
        for (const kw of keywords) {
            if (lower.includes(kw)) {
                // Longer keyword matches score higher
                score += kw.split(' ').length * 0.15;
            }
        }
        if (score > 0) {
            results.push({ tool, score: Math.min(score, 1.0) });
        }
    }
    return results.sort((a, b) => b.score - a.score);
}
/** Extract NLI entities from the resolved input text. */
function extractEntities(input) {
    const entities = [];
    // Spec IDs
    for (const specId of extractSpecIds(input)) {
        entities.push({ type: 'spec', value: specId, resolved: true, resolvedValue: specId });
    }
    // Language / framework mentions (simple heuristic)
    for (const fw of FRAMEWORK_KEYWORDS) {
        if (input.toLowerCase().includes(fw)) {
            entities.push({ type: 'framework', value: fw, resolved: true });
        }
    }
    return entities;
}
// ---------------------------------------------------------------------------
// Tool mapping
// ---------------------------------------------------------------------------
function buildToolMapping(tool, entities, confidence) {
    const autoParams = {};
    // Auto-fill params from resolved entities
    for (const entity of entities) {
        if (entity.type === 'spec' && entity.resolvedValue) {
            autoParams.specId = entity.resolvedValue;
        }
        if (entity.type === 'framework') {
            autoParams.framework = entity.value;
        }
    }
    return {
        primaryTool: tool,
        autoParams,
        missingParams: [],
        suggestedFlow: confidence < 0.5 ? `Did you mean to use ${tool}?` : undefined,
    };
}
// ---------------------------------------------------------------------------
// Main resolver
// ---------------------------------------------------------------------------
/**
 * Resolve a natural language input to an NLIIntentResolution.
 *
 * @param rawInput      The user's raw input text
 * @param ctx           Current session NLI context
 * @param availableSpecIds List of spec IDs in the project for disambiguation
 */
export function resolveNLI(rawInput, ctx, availableSpecIds = []) {
    // 1. Language detection
    const detectedLanguage = detectLanguage(rawInput);
    // 2. Pronoun resolution
    const { resolved: resolvedInput } = resolvePronounsInText(rawInput, ctx);
    // 3. Entity synonym normalization
    const normalizedInput = resolveEntitySynonyms(resolvedInput);
    // 4. Check learned user patterns first (highest priority)
    const patternMatches = matchUserPatterns(normalizedInput, ctx.userPatterns);
    const topPattern = patternMatches[0];
    // 5. Score against intent patterns
    const toolScores = scoreTools(normalizedInput);
    const topTool = topPattern && topPattern.confidence >= 0.8
        ? topPattern.pattern.mappedTool
        : (toolScores[0]?.tool ?? 'list_specs');
    const confidence = topPattern?.confidence ?? toolScores[0]?.score ?? 0.3;
    // 6. Extract entities
    const entities = extractEntities(normalizedInput);
    // 7. Detect SPEC-XXX references for disambiguation
    const specRefs = extractSpecIds(normalizedInput);
    const disambiguationOptions = buildDisambiguationOptions(specRefs, availableSpecIds);
    // 8. Build intent
    const intent = {
        primary: topTool,
        modifiers: [],
        entities,
        sentiment: normalizedInput.includes('?') ? 'question' : 'directive',
    };
    // 9. Tool mapping
    const toolMapping = buildToolMapping(topTool, entities, confidence);
    return {
        rawInput,
        detectedLanguage,
        intent,
        confidence,
        ambiguityResolution: disambiguationOptions.length > 1
            ? disambiguationOptions.map((o) => `${o.label}: ${o.reason}`).join('; ')
            : undefined,
        toolMapping,
        contextUsed: ctx,
    };
}
// ---------------------------------------------------------------------------
// Disambiguation
// ---------------------------------------------------------------------------
/** Build disambiguation options when multiple spec IDs could match. */
function buildDisambiguationOptions(specRefs, availableSpecIds) {
    if (specRefs.length > 0) {
        return specRefs
            .filter((id) => availableSpecIds.length === 0 || availableSpecIds.includes(id))
            .map((id) => ({
            label: id,
            specId: id,
            reason: 'Referenced explicitly in input',
        }));
    }
    return [];
}
/**
 * Generate disambiguation prompt for ambiguous inputs.
 * Returns null if no disambiguation is needed.
 */
export function buildDisambiguationPrompt(options, detectedLanguage) {
    if (options.length <= 1) {
        return null;
    }
    const isSpanish = detectedLanguage === 'es';
    const header = isSpanish ? '¿A cuál de estas specs te refieres?' : 'Which spec did you mean?';
    const list = options.map((o, i) => `  ${i + 1}. ${o.label}`).join('\n');
    return `${header}\n${list}`;
}
//# sourceMappingURL=nli-resolver.js.map