/**
 * Fuzzy string matching utilities
 *
 * Inspired by mistral-vibe's search_replace tool that uses difflib.SequenceMatcher
 * to find similar strings when exact matches fail.
 */
/**
 * Calculate the Longest Common Subsequence (LCS) length between two strings
 * Used for sequence matching similar to Python's difflib.SequenceMatcher
 */
function lcsLength(a, b) {
    const m = a.length;
    const n = b.length;
    // Use only two rows for space optimization
    let prev = new Array(n + 1).fill(0);
    let curr = new Array(n + 1).fill(0);
    for (let i = 1; i <= m; i++) {
        for (let j = 1; j <= n; j++) {
            if (a[i - 1] === b[j - 1]) {
                curr[j] = prev[j - 1] + 1;
            }
            else {
                curr[j] = Math.max(prev[j], curr[j - 1]);
            }
        }
        [prev, curr] = [curr, prev];
    }
    return prev[n];
}
/**
 * Calculate similarity ratio between two strings
 * Returns a value between 0 and 1, similar to difflib.SequenceMatcher.ratio()
 */
export function calculateSimilarity(a, b) {
    if (a === b)
        return 1.0;
    if (a.length === 0 || b.length === 0)
        return 0.0;
    const lcs = lcsLength(a, b);
    return (2 * lcs) / (a.length + b.length);
}
/**
 * Normalize a string for fuzzy comparison
 */
function normalizeString(str, normalizeWhitespace) {
    let result = str;
    if (normalizeWhitespace) {
        // Normalize line endings
        result = result.replace(/\r\n/g, '\n');
        // Normalize multiple spaces to single space
        result = result.replace(/[ \t]+/g, ' ');
        // Normalize multiple newlines
        result = result.replace(/\n{3,}/g, '\n\n');
        // Trim each line
        result = result.split('\n').map(line => line.trim()).join('\n');
    }
    return result;
}
/**
 * Find fuzzy matches for a search string in content
 *
 * @param content - The content to search in
 * @param searchStr - The string to search for
 * @param options - Fuzzy match options
 * @returns Array of fuzzy match results sorted by similarity (highest first)
 */
export function findFuzzyMatches(content, searchStr, options = {}) {
    const { threshold = 0.9, maxResults = 5, normalizeWhitespace = true, } = options;
    const results = [];
    const lines = content.split('\n');
    const searchLines = searchStr.split('\n');
    const searchLineCount = searchLines.length;
    // Normalize search string for comparison
    const normalizedSearch = normalizeString(searchStr, normalizeWhitespace);
    // Slide a window of the same size as searchStr through content
    for (let startLine = 0; startLine <= lines.length - searchLineCount; startLine++) {
        // Extract candidate chunk
        const candidateLines = lines.slice(startLine, startLine + searchLineCount);
        const candidate = candidateLines.join('\n');
        const normalizedCandidate = normalizeString(candidate, normalizeWhitespace);
        // Calculate similarity
        const similarity = calculateSimilarity(normalizedSearch, normalizedCandidate);
        if (similarity >= threshold) {
            results.push({
                match: candidate,
                similarity,
                startLine: startLine + 1, // Convert to 1-indexed
                endLine: startLine + searchLineCount,
                similarityPercent: `${Math.round(similarity * 100)}%`,
            });
        }
    }
    // Sort by similarity (highest first) and limit results
    results.sort((a, b) => b.similarity - a.similarity);
    return results.slice(0, maxResults);
}
/**
 * Find the best fuzzy match above threshold
 *
 * @param content - The content to search in
 * @param searchStr - The string to search for
 * @param threshold - Minimum similarity threshold (default 0.9)
 * @returns The best match or null if none found above threshold
 */
export function findBestFuzzyMatch(content, searchStr, threshold = 0.9) {
    const matches = findFuzzyMatches(content, searchStr, { threshold, maxResults: 1 });
    return matches.length > 0 ? matches[0] : null;
}
/**
 * Generate a unified diff-style output showing the difference between search and match
 */
export function generateFuzzyDiff(searchStr, matchStr, filePath, matchResult) {
    const searchLines = searchStr.split('\n');
    const matchLines = matchStr.split('\n');
    const lines = [
        `🔍 Fuzzy match found (${matchResult.similarityPercent} similarity)`,
        `   Lines ${matchResult.startLine}-${matchResult.endLine} in ${filePath}`,
        '',
        '--- Expected (search string)',
        '+++ Found (actual content)',
        `@@ -1,${searchLines.length} +1,${matchLines.length} @@`,
    ];
    // Simple line-by-line diff
    const maxLen = Math.max(searchLines.length, matchLines.length);
    for (let i = 0; i < maxLen; i++) {
        const searchLine = searchLines[i];
        const matchLine = matchLines[i];
        if (searchLine === matchLine) {
            lines.push(` ${matchLine ?? ''}`);
        }
        else if (searchLine !== undefined && matchLine !== undefined) {
            lines.push(`-${searchLine}`);
            lines.push(`+${matchLine}`);
        }
        else if (searchLine !== undefined) {
            lines.push(`-${searchLine}`);
        }
        else if (matchLine !== undefined) {
            lines.push(`+${matchLine}`);
        }
    }
    return lines.join('\n');
}
/**
 * Suggest fixes for common whitespace issues
 */
export function suggestWhitespaceFixes(searchStr, content) {
    const suggestions = [];
    // Check for line ending differences
    if (searchStr.includes('\r\n') && !content.includes('\r\n')) {
        suggestions.push('Your search string uses Windows line endings (CRLF), but the file uses Unix line endings (LF)');
    }
    else if (!searchStr.includes('\r\n') && content.includes('\r\n')) {
        suggestions.push('The file uses Windows line endings (CRLF), but your search string uses Unix line endings (LF)');
    }
    // Check for trailing whitespace
    const searchHasTrailing = searchStr.split('\n').some(line => line !== line.trimEnd());
    const contentHasTrailing = content.split('\n').some(line => line !== line.trimEnd());
    if (searchHasTrailing !== contentHasTrailing) {
        suggestions.push('There may be differences in trailing whitespace');
    }
    // Check for indentation style
    const searchUsesTabs = searchStr.includes('\t');
    const contentUsesTabs = content.includes('\t');
    if (searchUsesTabs && !contentUsesTabs) {
        suggestions.push('Your search string uses tabs, but the file uses spaces for indentation');
    }
    else if (!searchUsesTabs && contentUsesTabs) {
        suggestions.push('The file uses tabs, but your search string uses spaces for indentation');
    }
    return suggestions;
}
//# sourceMappingURL=fuzzy-match.js.map