#!/usr/bin/env node

/**
 * apply-claude-exports-fuzzy.js
 *
 * Applique les exports Claude avec fuzzy matching amélioré
 *
 * AMÉLIORATIONS:
 * - Normalisation des line endings (\r\n, \r, \n → \n unifié)
 * - Ignore les différences d'espacement (espaces multiples, tabs)
 * - Score de similarité abaissé à 85% pour plus de flexibilité
 * - Matching robuste qui ne casse pas sur les variations d'espaces
 *
 * Usage:
 *   node tools/apply-claude-exports-fuzzy.js           # Apply changes
 *   node tools/apply-claude-exports-fuzzy.js --dry-run # Preview only
 */

const fs = require('fs');
const path = require('path');

const EXPORTS_DIR = path.join(__dirname, '../claude-exports-last-3-days');
const LOGS_DIR = path.join(__dirname, '../logs');

// Créer dossier logs si nécessaire
if (!fs.existsSync(LOGS_DIR)) {
    fs.mkdirSync(LOGS_DIR, { recursive: true });
}

// Fichier de log avec timestamp
const timestamp = new Date().toISOString().replace(/:/g, '-').replace(/\..+/, '');
const LOG_FILE = path.join(LOGS_DIR, `apply-exports-fuzzy-${timestamp}.log`);

// Configuration fuzzy matching
const FUZZY_CONFIG = {
  minSimilarity: 0.85,       // Minimum 85% de similarité pour accepter le match (abaissé de 95% pour plus de flexibilité)
  contextLines: 3,           // Lignes de contexte avant/après
  ignoreWhitespace: true,    // Ignorer les différences d'espacement (espaces multiples, tabs)
  ignoreComments: false,     // Ignorer les différences dans les commentaires
  normalizeLineEndings: true // Unifier \r\n, \r, \n en \n (activé par défaut)
};

/**
 * Logger dans console ET fichier
 */
function log(message, onlyFile = false) {
    const line = `${message}\n`;

    // Écrire dans le fichier
    fs.appendFileSync(LOG_FILE, line, 'utf-8');

    // Écrire aussi dans la console (sauf si onlyFile)
    if (!onlyFile) {
        process.stdout.write(line);
    }
}

/**
 * Parse un fichier de session pour extraire les tool uses
 */
function parseSessionFile(filePath) {
    const content = fs.readFileSync(filePath, 'utf-8');
    const tools = [];

    const jsonBlockRegex = /\[\s*\{[\s\S]*?"type":\s*"tool_use"[\s\S]*?\}\s*\]/g;
    const matches = content.match(jsonBlockRegex);

    if (!matches) return tools;

    for (const match of matches) {
        try {
            const parsed = JSON.parse(match);
            for (const item of parsed) {
                if (item.type === 'tool_use' && (item.name === 'Edit' || item.name === 'Write')) {
                    tools.push({
                        name: item.name,
                        input: item.input
                    });
                }
            }
        } catch (e) {
            // Skip invalid JSON
        }
    }

    return tools;
}

/**
 * Normaliser un texte complet pour la comparaison
 * - Unifie les line endings (\r\n, \r, \n → \n)
 * - Ignore les différences d'espacement selon config
 */
function normalizeText(text) {
    let normalized = text;

    // Unifier tous les retours à la ligne si configuré
    if (FUZZY_CONFIG.normalizeLineEndings) {
        normalized = normalized.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
    }

    if (FUZZY_CONFIG.ignoreWhitespace) {
        // Réduire les espaces/tabs multiples en un seul espace
        normalized = normalized.replace(/[ \t]+/g, ' ');
        // Nettoyer les espaces en début/fin de chaque ligne
        normalized = normalized.split('\n').map(line => line.trim()).join('\n');
    }

    return normalized;
}

/**
 * Normaliser une ligne pour la comparaison
 */
function normalizeLine(line) {
    if (FUZZY_CONFIG.ignoreWhitespace) {
        // Trim + réduire les espaces multiples à un seul
        return line.trim().replace(/\s+/g, ' ');
    }
    return line;
}

/**
 * Calculer la similarité entre deux chaînes (Levenshtein simplifié)
 */
function calculateSimilarity(str1, str2) {
    const len1 = str1.length;
    const len2 = str2.length;

    if (len1 === 0) return len2 === 0 ? 1.0 : 0.0;
    if (len2 === 0) return 0.0;

    // Matrice de distance
    const matrix = Array(len1 + 1).fill(null).map(() => Array(len2 + 1).fill(0));

    for (let i = 0; i <= len1; i++) matrix[i][0] = i;
    for (let j = 0; j <= len2; j++) matrix[0][j] = j;

    for (let i = 1; i <= len1; i++) {
        for (let j = 1; j <= len2; j++) {
            const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
            matrix[i][j] = Math.min(
                matrix[i - 1][j] + 1,      // deletion
                matrix[i][j - 1] + 1,      // insertion
                matrix[i - 1][j - 1] + cost // substitution
            );
        }
    }

    const distance = matrix[len1][len2];
    const maxLen = Math.max(len1, len2);
    return 1 - (distance / maxLen);
}

/**
 * Créer un diff détaillé ligne par ligne
 */
function createDiff(oldLines, newLines) {
    const diff = [];
    const maxLen = Math.max(oldLines.length, newLines.length);

    for (let i = 0; i < maxLen; i++) {
        const oldLine = oldLines[i];
        const newLine = newLines[i];

        if (oldLine === undefined) {
            // Ligne ajoutée
            diff.push({ type: 'add', line: newLine, lineNum: i });
        } else if (newLine === undefined) {
            // Ligne supprimée
            diff.push({ type: 'del', line: oldLine, lineNum: i });
        } else if (oldLine !== newLine) {
            // Ligne modifiée
            diff.push({ type: 'mod', oldLine, newLine, lineNum: i });
        } else {
            // Ligne identique
            diff.push({ type: 'same', line: oldLine, lineNum: i });
        }
    }

    return diff;
}

/**
 * Trouver la meilleure position pour un fuzzy match
 */
function findFuzzyMatch(content, oldString) {
    // 🔥 CRITICAL FIX: Unifier SEULEMENT line endings (comme dans applyEdit)
    // pour que les positions correspondent au même format de texte
    // On normalise les espaces SEULEMENT pour la COMPARAISON (ligne par ligne)
    const contentWithUnifiedLineEndings = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
    const oldStringWithUnifiedLineEndings = oldString.replace(/\r\n/g, '\n').replace(/\r/g, '\n');

    const contentLines = contentWithUnifiedLineEndings.split('\n');
    const oldLines = oldStringWithUnifiedLineEndings.split('\n');

    if (oldLines.length === 0) return null;

    let bestMatch = null;
    let bestScore = 0;

    // Chercher dans tout le contenu
    for (let startLine = 0; startLine <= contentLines.length - oldLines.length; startLine++) {
        const candidateLines = contentLines.slice(startLine, startLine + oldLines.length);

        // Calculer le score de similarité ligne par ligne
        let totalScore = 0;
        let matchedLines = 0;

        for (let i = 0; i < oldLines.length; i++) {
            const oldNorm = normalizeLine(oldLines[i]);
            const candidateNorm = normalizeLine(candidateLines[i]);

            const similarity = calculateSimilarity(oldNorm, candidateNorm);
            totalScore += similarity;
            if (similarity > 0.8) matchedLines++;
        }

        const avgScore = totalScore / oldLines.length;
        const matchRatio = matchedLines / oldLines.length;

        // Score combiné: moyenne de similarité + ratio de lignes matchées
        const combinedScore = (avgScore * 0.7) + (matchRatio * 0.3);

        if (combinedScore > bestScore && combinedScore >= FUZZY_CONFIG.minSimilarity) {
            bestScore = combinedScore;
            bestMatch = {
                startLine,
                endLine: startLine + oldLines.length,
                score: combinedScore,
                matchedLines,
                totalLines: oldLines.length
            };
        }
    }

    return bestMatch;
}

/**
 * Appliquer un Edit avec fuzzy matching
 */
function applyEdit(filePath, oldString, newString, dryRun = false) {
    try {
        if (!fs.existsSync(filePath)) {
            log(`⏭️  SKIP Edit - Fichier n'existe pas: ${filePath}`);
            return { success: false, reason: 'FILE_NOT_EXIST' };
        }

        const content = fs.readFileSync(filePath, 'utf-8');

        // 🔥 Essayer d'abord un match exact SANS normalisation (le plus rapide et sûr)
        if (content.includes(oldString)) {
            const newContent = content.replace(oldString, newString);

            if (!dryRun) {
                fs.writeFileSync(filePath, newContent, 'utf-8');
            }

            log(`✅ EDIT EXACT appliqué: ${filePath}`);
            return { success: true, reason: 'EXACT_MATCH', method: 'exact' };
        }

        // Si pas de match exact, essayer le fuzzy matching (avec normalisation)
        const fuzzyMatch = findFuzzyMatch(content, oldString);

        if (fuzzyMatch) {
            // 🔥 IMPORTANT: fuzzyMatch a trouvé les positions avec normalisation
            // Mais on applique le remplacement sur les versions ORIGINALES (espaces préservés)
            // On unifie SEULEMENT les line endings (\r\n → \n) pour que les positions correspondent

            // Unifier line endings UNIQUEMENT (garder espaces originaux)
            const contentWithUnifiedLineEndings = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
            const newStringWithUnifiedLineEndings = newString.replace(/\r\n/g, '\n').replace(/\r/g, '\n');

            const contentLines = contentWithUnifiedLineEndings.split('\n');
            const newLines = newStringWithUnifiedLineEndings.split('\n');

            // Capturer les lignes matchées ORIGINALES (AVANT remplacement)
            const matchedLines = contentLines.slice(fuzzyMatch.startLine, fuzzyMatch.endLine);

            // Remplacer la zone identifiée avec le patch ORIGINAL
            const before = contentLines.slice(0, fuzzyMatch.startLine);
            const after = contentLines.slice(fuzzyMatch.endLine);
            const newContent = [...before, ...newLines, ...after].join('\n');

            if (!dryRun) {
                fs.writeFileSync(filePath, newContent, 'utf-8');
            }

            log(`🎯 EDIT FUZZY appliqué: ${filePath} (score: ${(fuzzyMatch.score * 100).toFixed(1)}%, lignes ${fuzzyMatch.startLine}-${fuzzyMatch.endLine})`);

            // Créer un diff détaillé
            const diff = createDiff(matchedLines, newLines);

            log(`┌─ 📝 DIFF DÉTAILLÉ ────────────────────────────────────────────────`);
            diff.forEach((item, idx) => {
                const lineNum = String(fuzzyMatch.startLine + idx + 1).padStart(4, ' ');

                if (item.type === 'same') {
                    // Ligne identique
                    log(`│ ${lineNum} │   ${item.line}`);
                } else if (item.type === 'add') {
                    // Ligne ajoutée
                    log(`│ ${lineNum} │ + ${item.line}`);
                } else if (item.type === 'del') {
                    // Ligne supprimée
                    log(`│ ${lineNum} │ - ${item.line}`);
                } else if (item.type === 'mod') {
                    // Ligne modifiée - afficher les deux
                    log(`│ ${lineNum} │ - ${item.oldLine}`);
                    log(`│ ${lineNum} │ + ${item.newLine}`);
                }
            });
            log(`└────────────────────────────────────────────────────────────────────`);
            log('');

            return {
                success: true,
                reason: 'FUZZY_MATCH',
                method: 'fuzzy',
                score: fuzzyMatch.score,
                lines: `${fuzzyMatch.startLine}-${fuzzyMatch.endLine}`
            };
        }

        log(`⏭️  SKIP Edit - Aucun match trouvé: ${filePath}`);
        return { success: false, reason: 'NO_MATCH' };

    } catch (e) {
        log(`❌ ERREUR Edit sur ${filePath}: ${e.message}`);
        return { success: false, reason: 'ERROR', error: e.message };
    }
}

/**
 * Appliquer un Write sur un fichier
 */
function applyWrite(filePath, content, dryRun = false) {
    try {
        if (fs.existsSync(filePath)) {
            log(`⏭️  SKIP Write - Fichier existe déjà: ${filePath}`);
            return { success: false, reason: 'FILE_EXISTS' };
        }

        const dir = path.dirname(filePath);
        if (!fs.existsSync(dir)) {
            if (!dryRun) {
                fs.mkdirSync(dir, { recursive: true });
            }
        }

        if (!dryRun) {
            fs.writeFileSync(filePath, content, 'utf-8');
        }

        log(`✅ WRITE appliqué: ${filePath}`);
        return { success: true, reason: 'CREATED' };

    } catch (e) {
        log(`❌ ERREUR Write sur ${filePath}: ${e.message}`);
        return { success: false, reason: 'ERROR', error: e.message };
    }
}

/**
 * Main
 */
function main() {
    // Check for dry-run mode
    const dryRun = process.argv.includes('--dry-run');

    log(`📝 Logs sauvegardés dans: ${LOG_FILE}`);
    log('');

    if (dryRun) {
        log('🔍 MODE DRY-RUN: Aucun fichier ne sera modifié');
        log('');
    }

    log('🔄 Application des exports Claude avec FUZZY MATCHING...');
    log(`⚙️  Config: minSimilarity=${FUZZY_CONFIG.minSimilarity * 100}%, ignoreWhitespace=${FUZZY_CONFIG.ignoreWhitespace}`);
    log('');

    // Lire tous les fichiers de session
    const sessionFiles = fs.readdirSync(EXPORTS_DIR)
        .filter(f => f.endsWith('-session.md'))
        .sort((a, b) => {
            const numA = parseInt(a.split('-')[0]);
            const numB = parseInt(b.split('-')[0]);
            return numB - numA; // Ordre inverse: 15 -> 1
        });

    log(`📁 ${sessionFiles.length} fichiers de session trouvés`);
    log(`📋 Ordre de traitement: ${sessionFiles.join(', ')}`);
    log('');

    const stats = {
        totalEdits: 0,
        totalWrites: 0,
        exactMatches: 0,
        fuzzyMatches: 0,
        successWrites: 0,
        skipped: 0,
        errors: 0
    };

    for (const sessionFile of sessionFiles) {
        const filePath = path.join(EXPORTS_DIR, sessionFile);
        log('');
        log(`📄 Traitement de: ${sessionFile}`);

        const tools = parseSessionFile(filePath);
        log(`   ${tools.length} tool use(s) trouvé(s)`);

        for (const tool of tools) {
            if (tool.name === 'Edit') {
                stats.totalEdits++;
                const { file_path, old_string, new_string } = tool.input;
                const result = applyEdit(file_path, old_string, new_string, dryRun);

                if (result.success) {
                    if (result.method === 'exact') {
                        stats.exactMatches++;
                    } else if (result.method === 'fuzzy') {
                        stats.fuzzyMatches++;
                    }
                } else {
                    if (result.reason === 'ERROR') {
                        stats.errors++;
                    } else {
                        stats.skipped++;
                    }
                }
            } else if (tool.name === 'Write') {
                stats.totalWrites++;
                const { file_path, content } = tool.input;
                const result = applyWrite(file_path, content, dryRun);

                if (result.success) {
                    stats.successWrites++;
                } else {
                    stats.skipped++;
                }
            }
        }
    }

    log('');
    log('');
    log('📊 RÉSUMÉ:');
    log(`   Edit Exact:  ${stats.exactMatches}/${stats.totalEdits} appliqués`);
    log(`   Edit Fuzzy:  ${stats.fuzzyMatches}/${stats.totalEdits} appliqués`);
    log(`   Write:       ${stats.successWrites}/${stats.totalWrites} appliqués`);
    log(`   Skippés:     ${stats.skipped}`);
    log(`   Erreurs:     ${stats.errors}`);
    log(`   Total:       ${stats.exactMatches + stats.fuzzyMatches + stats.successWrites}/${stats.totalEdits + stats.totalWrites} opérations réussies`);
    log('');

    if (dryRun) {
        log('💡 Pour appliquer réellement, relancez sans --dry-run');
    } else {
        log('✨ Terminé!');
    }

    log('');
    log(`📝 Logs complets: ${LOG_FILE}`);
}

main();
