// ========================================
// FICHIER: MissingKeywords.js - Version Node.js
// Description: Génération automatique des mots-clés manquants
// ========================================

const { logSh } = require('./ErrorReporting');
const { callLLM } = require('./LLMManager');
const { logElementsList } = require('./selective-enhancement/SelectiveUtils');

// ============================================
// OPTION B: Détection et génération multi-variables
// Ajout conservatif - ne remplace pas l'existant
// ============================================

/**
 * Extraire les variables individuelles d'un resolvedContent
 * Ex: "[T+1_5 non défini][MC+1_5 non défini]" → ["T+1_5", "MC+1_5"]
 */
function extractIndividualPlaceholders(resolvedContent) {
  if (!resolvedContent || typeof resolvedContent !== 'string') {
    return [];
  }

  const regex = /\[([A-Z]+[+-]\d+_\d+) non (défini|résolu)\]/g;
  const placeholders = [];
  let match;

  while ((match = regex.exec(resolvedContent)) !== null) {
    const varName = match[1];
    if (!placeholders.includes(varName)) {
      placeholders.push(varName);
    }
  }

  return placeholders;
}

/**
 * Déterminer le type d'une variable (pour contexte de génération)
 */
function getVariableType(varName) {
  if (varName.startsWith('T+') || varName.startsWith('T-')) {
    return 'titre'; // Titre court
  } else if (varName.startsWith('MC+') || varName.startsWith('MC-')) {
    return 'mot-clé'; // Mot-clé SEO
  } else if (varName.startsWith('L+') || varName.startsWith('L-')) {
    return 'lien'; // Slug URL
  }
  return 'texte'; // Fallback
}

/**
 * Analyser quels éléments ont plusieurs placeholders
 * Retourne: { elementName: [variables], ... }
 */
function analyzeMultiPlaceholderElements(missingElements) {
  const multiVarElements = {};

  missingElements.forEach(element => {
    const placeholders = extractIndividualPlaceholders(element.currentContent);

    if (placeholders.length > 1) {
      multiVarElements[element.name] = placeholders;
      logSh(`   🔍 Élément multi-variables détecté: [${element.name}] a ${placeholders.length} variables: ${placeholders.join(', ')}`, 'DEBUG');
    }
  });

  return multiVarElements;
}

/**
 * Génère automatiquement les mots-clés manquants pour les éléments non définis
 * @param {Array} elements - Liste des éléments extraits
 * @param {Object} csvData - Données CSV avec personnalité
 * @returns {Object} Éléments mis à jour avec nouveaux mots-clés
 */
async function generateMissingKeywords(elements, csvData) {
  logSh('>>> GÉNÉRATION MOTS-CLÉS MANQUANTS <<<', 'INFO');

  // 1. IDENTIFIER tous les éléments manquants
  const missingElements = [];
  elements.forEach(element => {
    // Détecter les patterns de contenu manquant
    const isNonDefini = element.resolvedContent.includes('non défini');
    const isNonResolu = element.resolvedContent.includes('non résolu');
    const isEmpty = element.resolvedContent.trim() === '';

    // Détecter aussi les patterns comme [MC+1_5 non défini], [T+1_3 non défini], etc.
    const hasPlaceholder = /\[(MC|T|L)[+-]\d+_\d+ non défini\]/i.test(element.resolvedContent);

    if (isNonDefini || isNonResolu || isEmpty || hasPlaceholder) {
      logSh(`🔍 Élément manquant détecté: [${element.name}] = "${element.resolvedContent}"`, 'DEBUG');

      missingElements.push({
        tag: element.originalTag,
        name: element.name,
        type: element.type,
        currentContent: element.resolvedContent,
        context: getElementContext(element, elements, csvData)
      });
    }
  });

  if (missingElements.length === 0) {
    logSh('✅ Aucun mot-clé manquant détecté', 'INFO');
    return elements; // Retourner les éléments inchangés
  }

  logSh(`⚠️ ${missingElements.length} mots-clés manquants détectés:`, 'INFO');
  missingElements.forEach((missing, index) => {
    logSh(`   ${index + 1}. [${missing.name}] = "${missing.currentContent}"`, 'INFO');
  });
  
  // 2. ANALYSER le contexte global disponible
  const contextAnalysis = analyzeAvailableContext(elements, csvData);
  
  // 3. GÉNÉRER tous les manquants en UN SEUL appel IA
  let generatedKeywords = await callOpenAIForMissingKeywords(missingElements, contextAnalysis, csvData);

  // ✅ 3.5. RETRY pour éléments encore manquants
  if (generatedKeywords._missingElements && generatedKeywords._missingElements.length > 0) {
    const stillMissingNames = generatedKeywords._missingElements;
    logSh(`🔄 RETRY: Génération ciblée pour ${stillMissingNames.length} éléments manquants`, 'INFO');

    // Filtrer les éléments encore manquants
    const retryElements = missingElements.filter(e => stillMissingNames.includes(e.name));

    try {
      // Retry avec un prompt simplifié
      const retryKeywords = await callOpenAIForMissingKeywords(retryElements, contextAnalysis, csvData);

      // Merger les résultats du retry avec les résultats existants
      Object.keys(retryKeywords).forEach(key => {
        if (key !== '_missingElements' && key !== '_subVariables') {
          generatedKeywords[key] = retryKeywords[key];
        }
      });

      logSh(`✅ RETRY réussi: ${Object.keys(retryKeywords).length - (retryKeywords._subVariables ? 1 : 0)} éléments supplémentaires générés`, 'INFO');
    } catch (retryError) {
      logSh(`⚠️ RETRY échoué: ${retryError.message}`, 'WARN');
      logSh(`⚠️ Le workflow continuera avec les éléments disponibles`, 'WARN');
    }
  }

  // 4. METTRE À JOUR les éléments avec les nouveaux mots-clés
  const updatedElements = updateElementsWithKeywords(elements, generatedKeywords);

  logSh(`✅ Mots-clés manquants générés: ${Object.keys(generatedKeywords).length}`, 'INFO');

  // 📊 LOG COMPLET: Afficher TOUS les mots-clés (Google Sheets + Générés)
  logSh(`\n📋 RÉCAPITULATIF COMPLET DES MOTS-CLÉS AVANT GÉNÉRATION:`, 'INFO');

  // 1. Mots-clés récupérés depuis Google Sheets
  logSh(`\n🔹 MOTS-CLÉS GOOGLE SHEETS:`, 'INFO');
  logSh(`   MC0 (principal): "${csvData.mc0 || 'VIDE'}"`, 'INFO');
  logSh(`   T0 (titre principal): "${csvData.t0 || 'VIDE'}"`, 'INFO');
  logSh(`   T-1: "${csvData.tMinus1 || 'VIDE'}"`, 'INFO');
  logSh(`   L-1: "${csvData.lMinus1 || 'VIDE'}"`, 'INFO');

  if (csvData.mcPlus1) {
    const mcPlus1Array = csvData.mcPlus1.split(',').map(s => s.trim());
    logSh(`   MC+1 (${mcPlus1Array.length} valeurs):`, 'INFO');
    mcPlus1Array.forEach((val, idx) => {
      logSh(`      [${idx + 1}] "${val}"`, 'INFO');
    });
  }

  if (csvData.tPlus1) {
    const tPlus1Array = csvData.tPlus1.split(',').map(s => s.trim());
    logSh(`   T+1 (${tPlus1Array.length} valeurs):`, 'INFO');
    tPlus1Array.forEach((val, idx) => {
      logSh(`      [${idx + 1}] "${val}"`, 'INFO');
    });
  }

  if (csvData.lPlus1) {
    const lPlus1Array = csvData.lPlus1.split(',').map(s => s.trim());
    logSh(`   L+1 (${lPlus1Array.length} valeurs):`, 'INFO');
    lPlus1Array.forEach((val, idx) => {
      logSh(`      [${idx + 1}] "${val}"`, 'INFO');
    });
  }

  // 2. Mots-clés générés par l'IA
  logSh(`\n🔹 MOTS-CLÉS GÉNÉRÉS PAR IA:`, 'INFO');
  const subVariables = generatedKeywords._subVariables || {};
  Object.keys(generatedKeywords).forEach(key => {
    if (key === '_subVariables' || key === '_missingElements') return;
    logSh(`   [${key}]: "${generatedKeywords[key]}"`, 'INFO');
  });

  if (Object.keys(subVariables).length > 0) {
    logSh(`\n   📌 Sous-variables (${Object.keys(subVariables).length}):`, 'INFO');
    Object.keys(subVariables).forEach(key => {
      logSh(`      [${key}]: "${subVariables[key]}"`, 'INFO');
    });
  }

  // 📊 LOG COMPLET: Afficher TOUS les éléments combinés (Google Sheets + IA)
  logElementsList(updatedElements, 'LISTE COMPLÈTE COMBINÉE (Google Sheets + IA)', generatedKeywords);

  return updatedElements;
}

/**
 * Analyser le contexte disponible pour guider la génération
 * @param {Array} elements - Tous les éléments
 * @param {Object} csvData - Données CSV
 * @returns {Object} Analyse contextuelle
 */
function analyzeAvailableContext(elements, csvData) {
  const availableKeywords = [];
  const availableContent = [];

  // ✅ NOUVEAU : Inclure les mots-clés de csvData (incluant ceux générés à l'étape 0)
  if (csvData.mcPlus1) {
    availableKeywords.push(...csvData.mcPlus1.split(',').map(s => s.trim()).filter(s => s));
  }
  if (csvData.tPlus1) {
    availableKeywords.push(...csvData.tPlus1.split(',').map(s => s.trim()).filter(s => s));
  }
  if (csvData.lPlus1) {
    availableKeywords.push(...csvData.lPlus1.split(',').map(s => s.trim()).filter(s => s));
  }
  if (csvData.tMinus1) {
    availableKeywords.push(csvData.tMinus1);
  }

  // Récupérer tous les mots-clés/contenu déjà disponibles depuis les éléments
  elements.forEach(element => {
    if (element.resolvedContent &&
        !element.resolvedContent.includes('non défini') &&
        !element.resolvedContent.includes('non résolu') &&
        element.resolvedContent.trim() !== '') {

      if (element.type.includes('titre')) {
        availableKeywords.push(element.resolvedContent);
      } else {
        availableContent.push(element.resolvedContent.substring(0, 100));
      }
    }
  });

  return {
    mainKeyword: csvData.mc0,
    mainTitle: csvData.t0,
    availableKeywords: availableKeywords, // ✅ Maintenant inclut csvData + elements
    availableContent: availableContent,
    theme: csvData.mc0 // Thème principal
  };
}

/**
 * Obtenir le contexte spécifique d'un élément
 * @param {Object} element - Élément à analyser
 * @param {Array} allElements - Tous les éléments
 * @param {Object} csvData - Données CSV
 * @returns {Object} Contexte de l'élément
 */
function getElementContext(element, allElements, csvData) {
  const context = {
    elementType: element.type,
    hierarchyLevel: element.name,
    nearbyElements: []
  };

  // Trouver les éléments proches dans la hiérarchie
  const elementParts = element.name.split('_');
  if (elementParts.length >= 2) {
    const baseLevel = elementParts.slice(0, 2).join('_'); // Ex: "Titre_H2", "Txt_H2"

    // Récupérer TOUS les éléments du même type (même baseLevel) qui sont déjà résolus
    allElements.forEach(otherElement => {
      // Vérifier que l'élément est du même type et déjà résolu
      if (otherElement.name !== element.name && // Pas l'élément lui-même
          otherElement.name.startsWith(baseLevel) &&
          otherElement.resolvedContent &&
          !otherElement.resolvedContent.includes('non défini') &&
          !otherElement.resolvedContent.includes('non résolu') &&
          otherElement.resolvedContent.trim() !== '') {

        context.nearbyElements.push(otherElement.resolvedContent);
      }
    });
  }

  return context;
}

/**
 * Construire le contexte hiérarchique compact pour un élément
 * @param {Object} missing - Élément manquant
 * @param {Array} allMissingElements - Tous les éléments manquants
 * @param {Object} csvData - Données CSV
 * @returns {string} Contexte compact (ex: "H2 existants: 'Titre1', 'Titre2'")
 */
function buildHierarchicalContext(missing, allMissingElements, csvData) {
  const elementName = missing.name;
  const parts = elementName.split('_');

  // Récupérer le contexte depuis missing.context.nearbyElements (déjà calculé par getElementContext)
  const nearbyElements = missing.context?.nearbyElements || [];

  // Déterminer le type d'élément
  if (elementName.startsWith('Titre_H2')) {
    // Pour un titre H2 : afficher les autres titres H2 existants
    const existingH2 = nearbyElements.filter(el => el && el.length > 0).slice(0, 3);
    if (existingH2.length > 0) {
      const shortened = existingH2.map(t => t.length > 30 ? t.substring(0, 30) + '...' : t);
      return `H2 existants: ${shortened.map(t => `"${t}"`).join(', ')}`;
    }
    return 'Premier H2 du document';

  } else if (elementName.startsWith('Titre_H3')) {
    // Pour un titre H3 : afficher les autres H3 + parent H2
    const existingH3 = nearbyElements.filter(el => el && el.length > 0).slice(0, 2);
    if (existingH3.length > 0) {
      const shortened = existingH3.map(t => t.length > 30 ? t.substring(0, 30) + '...' : t);
      return `H3 existants: ${shortened.map(t => `"${t}"`).join(', ')}`;
    }
    return 'Premier H3 dans cette section';

  } else if (elementName.startsWith('Txt_')) {
    // Pour un texte : afficher MC associé + titre parent + autres textes
    const level = elementName.match(/Txt_(H\d+)_(\d+)/);
    if (level) {
      const hierarchyLevel = level[1]; // H2, H3, etc.
      const index = parseInt(level[2]); // 6, 7, etc.

      // Chercher le MC correspondant (MC+1_6 pour Txt_H2_6)
      const mcKey = `MC+1_${index}`;
      let mcValue = null;
      if (csvData.mcPlus1) {
        const mcArray = csvData.mcPlus1.split(',').map(s => s.trim());
        if (mcArray[index - 1]) {
          mcValue = mcArray[index - 1];
        }
      }

      // Chercher le titre parent
      const titleName = `Titre_${hierarchyLevel}_${index}`;
      const parentTitle = nearbyElements.find(el => el.includes(titleName)) || nearbyElements[0];

      let context = '';
      if (mcValue) {
        const shortMc = mcValue.length > 25 ? mcValue.substring(0, 25) + '...' : mcValue;
        context = `MC: "${shortMc}"`;
      }
      if (parentTitle) {
        const shortTitle = parentTitle.length > 25 ? parentTitle.substring(0, 25) + '...' : parentTitle;
        context += (context ? ' | ' : '') + `Parent: "${shortTitle}"`;
      }
      if (nearbyElements.length > 1) {
        context += (context ? ' | ' : '') + `${nearbyElements.length - 1} autres textes`;
      }
      return context || 'Premier texte';
    }

  } else if (elementName.startsWith('Faq_')) {
    // Pour une FAQ : compter les FAQ existantes
    const faqCount = nearbyElements.filter(el => el && el.length > 0).length;
    if (faqCount > 0) {
      return `${faqCount} FAQ existantes`;
    }
    return 'Première FAQ';
  }

  // Fallback : afficher le nombre d'éléments proches
  if (nearbyElements.length > 0) {
    return `${nearbyElements.length} éléments similaires existants`;
  }
  return '';
}

/**
 * Appel IA pour générer tous les mots-clés manquants en un seul batch
 * @param {Array} missingElements - Éléments manquants
 * @param {Object} contextAnalysis - Analyse contextuelle
 * @param {Object} csvData - Données CSV avec personnalité
 * @returns {Object} Mots-clés générés
 */
async function callOpenAIForMissingKeywords(missingElements, contextAnalysis, csvData) {
  const personality = csvData.personality;

  // ✅ OPTION B: Détecter les éléments avec plusieurs variables
  const multiVarElements = analyzeMultiPlaceholderElements(missingElements);
  const hasMultiVarElements = Object.keys(multiVarElements).length > 0;

  if (hasMultiVarElements) {
    logSh(`🔍 ${Object.keys(multiVarElements).length} éléments avec variables multiples détectés`, 'INFO');
  }

  let prompt = `Tu es ${personality.nom} (${personality.description}). Style: ${personality.style}

MISSION: GÉNÈRE ${missingElements.length} MOTS-CLÉS/EXPRESSIONS MANQUANTS pour ${contextAnalysis.mainKeyword}

CONTEXTE:
- Sujet principal: ${contextAnalysis.mainKeyword}
- Mots-clés existants: ${contextAnalysis.availableKeywords.slice(0, 3).join(', ')}

🎯 IMPÉRATIF DIVERSITÉ ET SPÉCIFICITÉ:
- VARIE les angles: technique, usage, avantages, comparaisons, stratégies, communauté, équipement
- ÉVITE les répétitions "${contextAnalysis.mainKeyword} X, ${contextAnalysis.mainKeyword} Y, ${contextAnalysis.mainKeyword} Z"
- EXPLORE différents aspects selon le domaine du sujet
- Exemples d'angles variés:
  * Produits physiques: matériaux, formats, finitions, usages, durabilité
  * Jeux vidéo: stratégies, maps, armes, personnages, équipes, tournois, guides
  * Services: méthodes, outils, processus, résultats, comparaisons
- Sois SPÉCIFIQUE, pas générique

ÉLÉMENTS MANQUANTS:
`;

  missingElements.forEach((missing, index) => {
    const placeholders = extractIndividualPlaceholders(missing.currentContent);

    // Construire le contexte hiérarchique compact
    const hierarchyContext = buildHierarchicalContext(missing, missingElements, csvData);

    // ✅ OPTION B: Si l'élément a plusieurs variables, détailler chacune
    if (placeholders.length > 1) {
      prompt += `${index + 1}. [${missing.name}] a ${placeholders.length} variables distinctes:\n`;
      placeholders.forEach(varName => {
        const varType = getVariableType(varName);
        prompt += `   - [${missing.name}_${varName}] (type: ${varType}) → Génère un ${varType}\n`;
      });
    } else {
      // Format compact avec hiérarchie
      const elementType = missing.type || 'élément';
      if (hierarchyContext) {
        prompt += `${index + 1}. [${missing.name}] (${elementType}) — ${hierarchyContext}\n`;
      } else {
        prompt += `${index + 1}. [${missing.name}] (${elementType})\n`;
      }
    }
  });

  prompt += `\nCONSIGNES:
- Thème principal: ${contextAnalysis.mainKeyword}
- Génère des mots-clés/expressions SEO naturels et VARIÉS
- 🎯 DIVERSITÉ MAXIMALE: Chaque mot-clé doit explorer un angle DIFFÉRENT
- ÉVITE absolument les patterns répétitifs comme "${contextAnalysis.mainKeyword} + adjectif"
- Si un élément a plusieurs variables, génère des valeurs COMPLÈTEMENT DIFFÉRENTES
- Pour les liens (type: lien): slugs URL courts (ex: /guide-debutant, /meilleurs-conseils)
- Pour les titres (type: titre): courts et accrocheurs (5-10 mots max)
- Pour les mots-clés (type: mot-clé): expressions SEO spécifiques et variées

EXEMPLES DE DIVERSITÉ ATTENDUE:
- ❌ MAL: "Plaque dibond", "Plaque aluminium", "Plaque personnalisée"
- ✅ BIEN: "Dibond haute résistance", "Finitions gravure laser", "Sur-mesure extérieur"

⚠️ CRITIQUE: Tu DOIS générer TOUS les ${missingElements.length} éléments listés ci-dessus.
Ne saute aucun élément, même si tu génères des sous-variables pour certains.

FORMAT DE RÉPONSE EXACT (RESPECTE LES CROCHETS):`;

  // ✅ Donner 3 exemples concrets de la vraie liste
  const exampleCount = Math.min(3, missingElements.length);
  for (let i = 0; i < exampleCount; i++) {
    const example = missingElements[i];
    prompt += `
[${example.name}]
${example.type.includes('titre') ? 'Titre accrocheur 5-10 mots' : example.type.includes('question') ? 'Question naturelle?' : example.type.includes('reponse') ? 'Réponse claire et concise' : 'Expression SEO spécifique'}
`;
  }

  prompt += `
... Continue avec TOUS les ${missingElements.length} éléments de la liste ci-dessus ...

⚠️ IMPORTANT: Utilise EXACTEMENT les noms entre crochets [Titre_H2_3], [Txt_H2_6], etc. Ne change RIEN aux noms!`;

  try {
    logSh('🤖 Appel LLM pour génération mots-clés manquants...', 'INFO');

    // Utilisation du LLM Manager avec fallback (le prompt/réponse seront loggés par LLMManager)
    const response = await callLLM('gpt-4o-mini', prompt, {
      temperature: 0.7,
      maxTokens: 3000  // ✅ Augmenté de 2000 à 3000 pour éviter les coupures
    }, personality);

    // Parser la réponse
    const generatedKeywords = parseMissingKeywordsResponse(response, missingElements);

    logSh(`✅ ${Object.keys(generatedKeywords).length} mots-clés générés avec succès`, 'INFO');

    return generatedKeywords;

  } catch (error) {
    logSh(`❌ FATAL: Génération mots-clés manquants échouée: ${error}`, 'ERROR');
    throw new Error(`FATAL: Génération mots-clés LLM impossible - arrêt du workflow: ${error}`);
  }
}

/**
 * Parser la réponse IA pour extraire les mots-clés générés
 * @param {string} response - Réponse de l'IA
 * @param {Array} missingElements - Éléments manquants
 * @returns {Object} Mots-clés parsés
 */
function parseMissingKeywordsResponse(response, missingElements) {
  logSh('🔍 Début du parsing de la réponse LLM...', 'DEBUG');

  const results = {};
  const subVariableResults = {}; // ✅ OPTION B: Stocker les sous-variables séparément

  const regex = /\[([^\]]+)\]\s*\n([^[]*?)(?=\n\[|$)/gs;
  let match;

  while ((match = regex.exec(response)) !== null) {
    const elementName = match[1].trim();
    let generatedKeyword = match[2].trim();

    // ✅ Nettoyer les commentaires markdown et lignes parasites
    // Supprimer les commentaires qui commencent par #
    generatedKeyword = generatedKeyword
      .split('\n')
      .filter(line => !line.trim().startsWith('#'))  // Supprimer lignes commençant par #
      .filter(line => line.trim() !== '')             // Supprimer lignes vides
      .join('\n')
      .trim();

    // Ignorer si vide après nettoyage
    if (!generatedKeyword) {
      logSh(`   ⚠️ Élément [${elementName}] ignoré (vide après nettoyage)`, 'DEBUG');
      continue;
    }

    // ✅ OPTION B: Détecter si c'est une sous-variable (format: Txt_H2_6_T+1_5)
    if (elementName.includes('_T+') || elementName.includes('_MC+') || elementName.includes('_L+') ||
        elementName.includes('_T-') || elementName.includes('_MC-') || elementName.includes('_L-')) {

      // C'est une sous-variable
      subVariableResults[elementName] = generatedKeyword;
      logSh(`   ✓ Sous-variable générée [${elementName}]: "${generatedKeyword}"`, 'INFO');
    } else {
      // Comportement normal
      results[elementName] = generatedKeyword;
      logSh(`   ✓ Mot-clé généré [${elementName}]: "${generatedKeyword}"`, 'INFO');
    }
  }

  // ✅ OPTION B: Stocker les sous-variables dans results pour y accéder plus tard
  if (Object.keys(subVariableResults).length > 0) {
    results._subVariables = subVariableResults;
    logSh(`📊 ${Object.keys(subVariableResults).length} sous-variables extraites`, 'INFO');
  }

  logSh(`📊 Parsing terminé: ${Object.keys(results).length} mots-clés extraits`, 'INFO');

  // ✅ OPTION B: Validation améliorée pour gérer les sous-variables
  const uniqueNames = [...new Set(missingElements.map(e => e.name))];
  const parsedCount = Object.keys(results).length - (results._subVariables ? 1 : 0); // Exclure _subVariables du compte
  const subVarCount = Object.keys(subVariableResults).length;

  logSh(`🔢 Validation: ${parsedCount} éléments principaux, ${subVarCount} sous-variables / ${uniqueNames.length} tags uniques attendus`, 'INFO');

  // Vérifier qu'on a au moins récupéré QUELQUE CHOSE
  if (parsedCount === 0 && subVarCount === 0) {
    logSh(`❌ FATAL: Aucun mot-clé parsé de la réponse LLM`, 'ERROR');
    logSh(`Réponse reçue: ${response.substring(0, 500)}...`, 'ERROR');
    throw new Error(`FATAL: Parsing mots-clés échoué complètement - arrêt du workflow`);
  }

  // ✅ OPTION B: Si on a des sous-variables, vérifier qu'on couvre tous les éléments
  if (subVarCount > 0) {
    // Pour chaque élément unique, vérifier qu'il a soit une valeur principale, soit des sous-variables
    const elementsManquants = [];

    uniqueNames.forEach(name => {
      const hasMainValue = results[name];
      const hasSubVariables = Object.keys(subVariableResults).some(key => key.startsWith(`${name}_`));

      if (!hasMainValue && !hasSubVariables) {
        elementsManquants.push(name);
      }
    });

    if (elementsManquants.length > 0) {
      logSh(`⚠️ AVERTISSEMENT: ${elementsManquants.length}/${uniqueNames.length} éléments n'ont ni valeur principale ni sous-variables`, 'WARN');
      logSh(`Éléments manquants:`, 'WARN');
      elementsManquants.forEach(name => logSh(`   - [${name}]`, 'WARN'));

      // ✅ Retourner les éléments manquants pour permettre un retry
      results._missingElements = elementsManquants;
      logSh(`💡 Ces éléments seront réessayés dans un second appel LLM`, 'INFO');
    }

    logSh(`✅ Validation OK: ${parsedCount} éléments + ${subVarCount} sous-variables couvrent ${uniqueNames.length} tags uniques`, 'INFO');
  } else {
    // Comportement classique si pas de sous-variables
    if (parsedCount < uniqueNames.length) {
      const manquants = uniqueNames.length - parsedCount;
      logSh(`⚠️ AVERTISSEMENT: Parsing incomplet - ${manquants}/${uniqueNames.length} tags uniques non parsés`, 'WARN');

      const elementsNonParses = uniqueNames.filter(name => !results[name]);
      logSh(`Éléments non parsés:`, 'WARN');
      elementsNonParses.forEach(name => logSh(`   - [${name}]`, 'WARN'));

      // ✅ Retourner les éléments manquants pour permettre un retry
      results._missingElements = elementsNonParses;
      logSh(`💡 Ces éléments seront réessayés dans un second appel LLM`, 'INFO');
    } else {
      logSh(`✅ Validation OK: ${parsedCount} mots-clés parsés pour ${uniqueNames.length} tags uniques`, 'INFO');
    }
  }

  return results;
}

/**
 * Re-résoudre les instructions en remplaçant tous les placeholders
 * SOLUTION B: Fix synchronisation resolvedContent <-> instructions
 * @param {string} instructionTemplate - Instructions avec placeholders
 * @param {Object} allGeneratedKeywords - Tous les mots-clés générés (tag => keyword)
 * @param {Object} element - L'élément courant pour obtenir son resolvedContent
 * @returns {string} Instructions avec placeholders remplacés
 */
function reResolveInstructions(instructionTemplate, allGeneratedKeywords, element) {
  if (!instructionTemplate || typeof instructionTemplate !== 'string') {
    return instructionTemplate;
  }

  // ✅ OPTION B: Récupérer les sous-variables si disponibles
  const subVariables = allGeneratedKeywords._subVariables || {};
  const hasSubVariables = Object.keys(subVariables).length > 0;

  let resolved = instructionTemplate;

  // ✅ OPTION B: Stratégie améliorée pour multi-variables
  resolved = resolved.replace(
    /\[([^\]]+) non (défini|résolu)\]/g,
    (match, varName) => {
      // 1. Chercher d'abord dans les sous-variables spécifiques à cet élément
      if (hasSubVariables && element && element.name) {
        const subVarKey = `${element.name}_${varName}`;
        if (subVariables[subVarKey]) {
          logSh(`      🔄 Placeholder résolu via sous-variable: "${match}" → "${subVariables[subVarKey]}"`, 'DEBUG');
          return subVariables[subVarKey];
        }
      }

      // 2. Chercher dans les mots-clés générés par nom de variable exact
      for (const [tagName, generatedKeyword] of Object.entries(allGeneratedKeywords)) {
        if (tagName === '_subVariables' || tagName === '_missingElements') continue; // Skip metadata

        // Match exact du varName dans le tagName
        if (tagName.includes(varName)) {
          logSh(`      🔄 Placeholder "${match}" résolu via tag exact [${tagName}] → "${generatedKeyword}"`, 'INFO');
          return generatedKeyword;
        }
      }

      // 3. Fallback: utiliser le resolvedContent global de l'élément (⚠️ peut causer des répétitions)
      if (element && element.resolvedContent && !element.resolvedContent.includes('non défini')) {
        logSh(`      ⚠️ Placeholder "${match}" résolu via resolvedContent fallback → "${element.resolvedContent}"`, 'WARNING');
        return element.resolvedContent;
      }

      // 4. Dernier fallback: garder le placeholder
      return match;
    }
  );

  return resolved;
}

/**
 * Mettre à jour les éléments avec les nouveaux mots-clés générés
 * @param {Array} elements - Éléments originaux
 * @param {Object} generatedKeywords - Nouveaux mots-clés
 * @returns {Array} Éléments mis à jour
 */
function updateElementsWithKeywords(elements, generatedKeywords) {
  logSh('🔄 Mise à jour des éléments avec les nouveaux mots-clés...', 'INFO');

  // 🔍 DEBUG: Afficher les clés disponibles dans generatedKeywords
  logSh(`🔍 DEBUG: Clés disponibles dans generatedKeywords:`, 'DEBUG');
  Object.keys(generatedKeywords).forEach(key => {
    if (key !== '_subVariables' && key !== '_missingElements') {
      logSh(`   - "${key}" → "${generatedKeywords[key]}"`, 'DEBUG');
    }
  });

  let updatedCount = 0;
  let instructionsUpdatedCount = 0;
  const subVariables = generatedKeywords._subVariables || {};

  const updatedElements = elements.map(element => {
    const newKeyword = generatedKeywords[element.name];

    // 🔍 DEBUG: Log détaillé pour TOUS les éléments manquants
    const isManquant = element.resolvedContent.includes('non défini') ||
                       element.resolvedContent.includes('non résolu') ||
                       element.resolvedContent.trim() === '';

    if (isManquant) {
      logSh(`   🔍 DEBUG MANQUANT: [${element.name}]`, 'WARN');
      logSh(`      📄 resolvedContent: "${element.resolvedContent}"`, 'WARN');
      logSh(`      🔑 Cherche dans generatedKeywords["${element.name}"]: ${newKeyword ? `TROUVÉ = "${newKeyword}"` : '❌ PAS TROUVÉ'}`, 'WARN');

      // Chercher une clé similaire
      const similarKeys = Object.keys(generatedKeywords).filter(key =>
        key !== '_subVariables' && key !== '_missingElements' &&
        (key.includes(element.name) || element.name.includes(key))
      );
      if (similarKeys.length > 0) {
        logSh(`      💡 Clés similaires trouvées: ${similarKeys.join(', ')}`, 'WARN');
      }
    }

    // ✅ OPTION B: Si pas de valeur principale mais des sous-variables existent
    const hasSubVariables = Object.keys(subVariables).some(key => key.startsWith(`${element.name}_`));

    if (newKeyword || hasSubVariables) {
      updatedCount++;

      // ✅ OPTION B: Si on a des sous-variables, construire une valeur résumée
      let finalKeyword = newKeyword;
      if (!finalKeyword && hasSubVariables) {
        // Combiner les sous-variables en une seule valeur pour resolvedContent
        const subVarKeys = Object.keys(subVariables).filter(key => key.startsWith(`${element.name}_`));
        finalKeyword = subVarKeys.map(key => subVariables[key]).join(' | ');
        logSh(`   ✓ Élément [${element.name}] construit depuis ${subVarKeys.length} sous-variables: "${finalKeyword.substring(0, 80)}..."`, 'DEBUG');
      } else {
        logSh(`   ✓ Élément [${element.name}] mis à jour: "${element.resolvedContent}" → "${newKeyword}"`, 'DEBUG');
      }

      // ✅ SOLUTION B: Re-résoudre aussi les instructions si elles existent
      const updatedElement = {
        ...element,
        resolvedContent: finalKeyword
      };

      if (element.instructions) {
        const beforeInstructions = element.instructions;
        updatedElement.instructions = reResolveInstructions(
          element.instructions,
          generatedKeywords,
          updatedElement  // ✅ Passer l'élément avec le nouveau resolvedContent
        );

        // Log si les instructions ont changé
        if (beforeInstructions !== updatedElement.instructions) {
          instructionsUpdatedCount++;
          logSh(`      ✨ Instructions mises à jour pour [${element.name}]`, 'DEBUG');
        }
      }

      return updatedElement;
    }

    return element;
  });

  logSh(`✅ ${updatedCount} éléments mis à jour avec nouveaux mots-clés`, 'INFO');
  if (instructionsUpdatedCount > 0) {
    logSh(`✅ ${instructionsUpdatedCount} instructions re-résolues avec nouveaux mots-clés`, 'INFO');
  }

  return updatedElements;
}

// ============================================
// NOUVELLE FONCTIONNALITÉ: Génération variables Google Sheets manquantes
// Génère les variables (MC+1_5, T+1_6, etc.) AVANT extraction pour avoir csvData complet
// ============================================

/**
 * Scanner le XML pour trouver toutes les variables demandées
 * @param {string} xmlTemplate - Template XML (peut être base64)
 * @returns {Object} { MC+1: [indices], T+1: [indices], L+1: [indices] }
 */
function scanXMLForVariables(xmlTemplate) {
  // Décoder le base64 si nécessaire
  const xmlString = xmlTemplate.startsWith('<?xml')
    ? xmlTemplate
    : Buffer.from(xmlTemplate, 'base64').toString('utf8');

  const requestedVariables = {
    'MC+1': new Set(),
    'T+1': new Set(),
    'L+1': new Set(),
    'MC-1': new Set(),
    'T-1': new Set(),
    'L-1': new Set()
  };

  // Chercher toutes les variables {{MC+1_X}}, {{T+1_X}}, {{L+1_X}}, etc.
  const regex = /\{\{(MC|T|L)([+-])1_(\d+)\}\}/gi;
  let match;

  while ((match = regex.exec(xmlString)) !== null) {
    const varType = match[1].toUpperCase(); // MC, T, L
    const sign = match[2]; // + ou -
    const index = parseInt(match[3]); // 1, 2, 3, etc.
    const key = `${varType}${sign}1`;

    requestedVariables[key].add(index);
  }

  // Convertir Sets en arrays triés
  Object.keys(requestedVariables).forEach(key => {
    requestedVariables[key] = Array.from(requestedVariables[key]).sort((a, b) => a - b);
  });

  return requestedVariables;
}

/**
 * Trouver les variables manquantes entre ce qui est demandé et ce qui existe
 * @param {Object} requestedVariables - Variables demandées par le XML
 * @param {Object} csvData - Données Google Sheets
 * @returns {Array} Liste des variables manquantes { varName, index, type }
 */
function findMissingVariables(requestedVariables, csvData) {
  const missingVariables = [];

  // Helper pour obtenir le nombre d'éléments existants dans csvData
  const getExistingCount = (key) => {
    const mapping = {
      'MC+1': 'mcPlus1',
      'T+1': 'tPlus1',
      'L+1': 'lPlus1',
      'MC-1': 'mcMinus1',
      'T-1': 'tMinus1',
      'L-1': 'lMinus1'
    };

    const csvKey = mapping[key];
    if (!csvData[csvKey]) return 0;

    return csvData[csvKey].split(',').map(s => s.trim()).filter(s => s.length > 0).length;
  };

  // Pour chaque type de variable (MC+1, T+1, etc.)
  Object.keys(requestedVariables).forEach(key => {
    const requestedIndices = requestedVariables[key];
    if (requestedIndices.length === 0) return;

    const existingCount = getExistingCount(key);
    const maxRequestedIndex = Math.max(...requestedIndices);

    // Si on demande des indices qui n'existent pas
    if (maxRequestedIndex > existingCount) {
      for (let i = existingCount + 1; i <= maxRequestedIndex; i++) {
        missingVariables.push({
          varName: `${key}_${i}`,
          index: i,
          type: key,
          fullKey: key
        });
      }
    }
  });

  return missingVariables;
}

/**
 * Mettre à jour csvData avec les mots-clés générés
 * @param {Object} csvData - Données CSV originales
 * @param {Object} generatedKeywords - Mots-clés générés { varName: value }
 * @returns {Object} csvData mis à jour
 */
function updateCsvDataWithKeywords(csvData, generatedKeywords) {
  const updatedCsvData = { ...csvData };

  // Mapping entre types de variables et clés csvData
  const mapping = {
    'MC+1': 'mcPlus1',
    'T+1': 'tPlus1',
    'L+1': 'lPlus1',
    'MC-1': 'mcMinus1',
    'T-1': 'tMinus1',
    'L-1': 'lMinus1'
  };

  // Organiser les mots-clés générés par type
  const organized = {};
  Object.keys(generatedKeywords).forEach(varName => {
    if (varName.startsWith('_')) return; // Skip metadata

    // Parser varName: "MC+1_5" → type="MC+1", index=5
    const match = varName.match(/^(MC|T|L)([+-])1_(\d+)$/i);
    if (!match) return;

    const type = `${match[1].toUpperCase()}${match[2]}1`;
    const index = parseInt(match[3]);

    if (!organized[type]) {
      organized[type] = {};
    }
    organized[type][index] = generatedKeywords[varName];
  });

  // Mettre à jour csvData pour chaque type
  Object.keys(organized).forEach(type => {
    const csvKey = mapping[type];
    if (!csvKey) return;

    // Récupérer les valeurs existantes
    const existing = updatedCsvData[csvKey]
      ? updatedCsvData[csvKey].split(',').map(s => s.trim()).filter(s => s.length > 0)
      : [];

    // Créer un array complet avec les nouvelles valeurs
    const maxIndex = Math.max(...Object.keys(organized[type]).map(i => parseInt(i)));
    const complete = [];

    for (let i = 1; i <= maxIndex; i++) {
      if (organized[type][i]) {
        // Nouvelle valeur générée
        complete.push(organized[type][i]);
      } else if (existing[i - 1]) {
        // Valeur existante
        complete.push(existing[i - 1]);
      } else {
        // Aucune valeur (ne devrait pas arriver)
        complete.push('');
      }
    }

    // Mettre à jour csvData
    updatedCsvData[csvKey] = complete.join(', ');
  });

  return updatedCsvData;
}

/**
 * Fonction principale: Générer les variables Google Sheets manquantes
 * @param {string} xmlTemplate - Template XML
 * @param {Object} csvData - Données Google Sheets
 * @returns {Object} csvData mis à jour avec toutes les variables
 */
async function generateMissingSheetVariables(xmlTemplate, csvData) {
  logSh('>>> GÉNÉRATION VARIABLES GOOGLE SHEETS MANQUANTES <<<', 'INFO');

  // 1. Scanner le XML pour trouver toutes les variables demandées
  const requestedVariables = scanXMLForVariables(xmlTemplate);

  logSh('📊 Variables demandées dans le XML:', 'INFO');
  Object.keys(requestedVariables).forEach(key => {
    if (requestedVariables[key].length > 0) {
      logSh(`   ${key}: indices ${requestedVariables[key].join(', ')}`, 'INFO');
    }
  });

  // 2. Comparer avec ce qui existe dans csvData
  const missingVariables = findMissingVariables(requestedVariables, csvData);

  if (missingVariables.length === 0) {
    logSh('✅ Aucune variable Google Sheets manquante', 'INFO');
    return csvData;
  }

  logSh(`⚠️ ${missingVariables.length} variables Google Sheets manquantes:`, 'INFO');
  missingVariables.forEach((missing, index) => {
    logSh(`   ${index + 1}. ${missing.varName} (type: ${getVariableType(missing.varName)})`, 'INFO');
  });

  // 3. Transformer en format compatible avec callOpenAIForMissingKeywords
  const pseudoElements = missingVariables.map(varInfo => ({
    name: varInfo.varName,
    currentContent: `[${varInfo.varName} non défini]`,
    type: getVariableType(varInfo.varName),
    context: {
      mainKeyword: csvData.mc0,
      elementType: getVariableType(varInfo.varName),
      hierarchyLevel: 'sheet_variable'
    }
  }));

  // 4. Préparer le contexte avec les mots-clés existants pour éviter les doublons
  const existingKeywords = [];

  // Extraire les mots-clés existants de csvData
  if (csvData.mcPlus1) {
    existingKeywords.push(...csvData.mcPlus1.split(',').map(s => s.trim()).filter(s => s));
  }
  if (csvData.tPlus1) {
    existingKeywords.push(...csvData.tPlus1.split(',').map(s => s.trim()).filter(s => s));
  }
  if (csvData.lPlus1) {
    existingKeywords.push(...csvData.lPlus1.split(',').map(s => s.trim()).filter(s => s));
  }

  logSh(`📌 Mots-clés existants à éviter: ${existingKeywords.join(', ')}`, 'DEBUG');

  // Créer le contexte avec les mots-clés existants
  const contextAnalysis = {
    mainKeyword: csvData.mc0,
    mainTitle: csvData.t0,
    availableKeywords: existingKeywords, // ✅ IMPORTANT : éviter les doublons
    availableContent: [],
    theme: csvData.mc0
  };

  logSh('🤖 Appel LLM pour génération des variables Google Sheets...', 'INFO');
  const generatedKeywords = await callOpenAIForMissingKeywords(
    pseudoElements,
    contextAnalysis,
    csvData
  );

  // 5. Mettre à jour csvData
  const updatedCsvData = updateCsvDataWithKeywords(csvData, generatedKeywords);

  logSh(`✅ csvData mis à jour avec ${Object.keys(generatedKeywords).length - (generatedKeywords._subVariables ? 1 : 0)} nouvelles variables`, 'INFO');

  // 6. Logger le résultat
  logSh('\n📋 RÉCAPITULATIF VARIABLES APRÈS GÉNÉRATION:', 'INFO');
  ['mcPlus1', 'tPlus1', 'lPlus1'].forEach(key => {
    if (updatedCsvData[key]) {
      const values = updatedCsvData[key].split(',').map(s => s.trim());
      logSh(`   ${key}: ${values.length} valeurs`, 'INFO');
      values.forEach((val, idx) => {
        logSh(`      [${idx + 1}] "${val}"`, 'INFO');
      });
    }
  });

  return updatedCsvData;
}

// Exports CommonJS
module.exports = {
  generateMissingKeywords,
  generateMissingSheetVariables  // ✅ Export de la nouvelle fonction
};