function highlightText(root, search) {
  if (!root || !search) return;

  // Si root est une chaîne, on l'interprète comme une classe CSS :
  // on applique le surlignement à toutes les <div> portant cette classe.
  if (typeof root === "string") {
    const divs = document.querySelectorAll(`div.${root}`);
    divs.forEach(div => highlightText(div, search));
    return;
  }

  function normalizeChar(char) {
    return char
      .normalize("NFD")
      .replace(/[\u0300-\u036f]/g, "")
      .toLowerCase();
  }

  function buildNormalizedMap(str) {
    let normalized = "";
    const map = [];

    for (let i = 0; i < str.length; i++) {
      const norm = normalizeChar(str[i]);
      normalized += norm;

      for (let j = 0; j < norm.length; j++) {
        map.push(i);
      }
    }

    return { normalized, map };
  }

  function getTextNodes(node, acc = []) {
    if (node.nodeType === Node.TEXT_NODE) {
      if (node.nodeValue.trim() !== "") {
        acc.push(node);
      }
      return acc;
    }

    if (node.nodeType === Node.ELEMENT_NODE) {
      const tag = node.tagName.toLowerCase();
      if (["script", "style", "noscript", "iframe"].includes(tag)) {
        return acc;
      }

      for (const child of node.childNodes) {
        getTextNodes(child, acc);
      }
    }

    return acc;
  }

  function wrapMatchesInTextNode(textNode, normalizedSearch) {
    const originalText = textNode.nodeValue;
    const { normalized, map } = buildNormalizedMap(originalText);

    const matches = [];
    let startIndex = 0;

    while (true) {
      const matchIndex = normalized.indexOf(normalizedSearch, startIndex);
      if (matchIndex === -1) break;

      const originalStart = map[matchIndex];
      const originalEnd = map[matchIndex + normalizedSearch.length - 1] + 1;

      matches.push([originalStart, originalEnd]);
      startIndex = matchIndex + normalizedSearch.length;
    }

    if (matches.length === 0) return;

    const fragment = document.createDocumentFragment();
    let lastIndex = 0;

    for (const [start, end] of matches) {
      if (start > lastIndex) {
        fragment.appendChild(
          document.createTextNode(originalText.slice(lastIndex, start))
        );
      }

      const span = document.createElement("span");
      span.className = "highlight";
      span.textContent = originalText.slice(start, end);
      fragment.appendChild(span);

      lastIndex = end;
    }

    if (lastIndex < originalText.length) {
      fragment.appendChild(
        document.createTextNode(originalText.slice(lastIndex))
      );
    }

    textNode.parentNode.replaceChild(fragment, textNode);
  }

  const normalizedSearch = buildNormalizedMap(search).normalized;
  if (!normalizedSearch) return;

  const textNodes = getTextNodes(root);
  for (const textNode of textNodes) {
    wrapMatchesInTextNode(textNode, normalizedSearch);
  }
}

function removeHighlight(root) {
  if (!root) return;

  // Si root est une chaîne, on supprime les surlignements dans toutes
  // les <div> portant cette classe CSS.
  if (typeof root === "string") {
    const divs = document.querySelectorAll(`div.${root}`);
    divs.forEach(div => removeHighlight(div));
    return;
  }

  const highlights = root.querySelectorAll("span.highlight");

  highlights.forEach(span => {
    const parent = span.parentNode;

    // Remplacer le span par son contenu texte
    parent.replaceChild(document.createTextNode(span.textContent), span);

    // Fusionner les nœuds texte adjacents (important pour garder un DOM propre)
    parent.normalize();
  });
}