(gina.ready(function onGinaReady($){
/**
 * Glossaire handler
 *
 * Filtre côté client la liste des termes du glossaire (#glossary-search).
 *
 * Le mock `glossaire.json` produit une suite linéaire de noeuds frères dans
 * `.factsheet-content .wrap > .bd.glossary` : un `<h2>` par lettre, un `<h3>`
 * par terme suivi de ses paragraphes de définition. Le filtrage regroupe
 * chaque `<h3>` avec ses frères jusqu'au prochain `<h2>`/`<h3>` puis masque
 * les groupes qui ne matchent pas. Les `<h2>` de lettre dont tous les termes
 * sont masqués sont également cachés.
 *
 * Early-return guard : ce handler est chargé sur la clé `factsheetsentry`
 * (toutes les fiches), mais sort immédiatement si l'input n'existe pas.
 */

var GlossaireHandler = ( function onGlossaireHandled() {
    var self = {};

    var $input       = null;
    var $empty       = null;
    var letterGroups = []; // [{ heading: <h2>, preceding: [Node, ...], terms: [termGroup, ...] }]
    var termGroups   = []; // [{ heading: <h3>, preceding: [Node, ...], content: [Node, ...], haystack: 'normalized' }]

    // Strip diacritics (é → e, à → a, ç → c, etc.) + lowercase.
    var normalize = function (s) {
        if (!s) return '';
        return s.toString().normalize('NFD').replace(/\p{Diacritic}/gu, '').toLowerCase();
    };

    // Build the haystack text for a term: heading text + every following
    // sibling node's textContent until the next heading. The `<span class="abbr">`
    // expansion inside h3 already lands in textContent — bonus match surface.
    //
    // Each term's markdown source carries a leading `---` (markdown separator),
    // which renders as an `<hr>` BEFORE the term's `<h3>`. We treat any node
    // sitting between the previous heading's content and the next heading
    // (typically `<hr>`, possibly stray whitespace nodes) as PRECEDING the
    // upcoming heading — so it hides together with the heading it announces,
    // not the term that ended above it. Otherwise filtering leaves orphan
    // `<hr>` separators floating in the visible result.
    var buildGroups = function (root) {
        letterGroups = [];
        termGroups   = [];

        var currentLetter    = null;
        var currentTerm      = null;
        var pendingPreceding = [];

        var children = root.children;
        for (var i = 0; i < children.length; i++) {
            var node = children[i];
            var tag  = node.tagName;

            // Skip persistent chrome (search bar + alphabetical index) — these
            // must stay visible regardless of filter state. If included in the
            // group walk they'd land in `pendingPreceding` (rendered before the
            // first <h2>) and hide together with letter A when it filters out.
            if (node.classList && (node.classList.contains('glossary-search') || node.classList.contains('glossary-index'))) {
                continue;
            }

            if (tag === 'H2') {
                currentLetter = {
                    heading   : node,
                    preceding : pendingPreceding,
                    terms     : []
                };
                letterGroups.push(currentLetter);
                pendingPreceding = [];
                currentTerm = null;
            } else if (tag === 'H3') {
                currentTerm = {
                    heading   : node,
                    preceding : pendingPreceding,
                    content   : [],
                    haystack  : normalize(node.textContent)
                };
                termGroups.push(currentTerm);
                if (currentLetter) currentLetter.terms.push(currentTerm);
                pendingPreceding = [];
            } else if (tag === 'HR') {
                // Separator always belongs to the heading that FOLLOWS it.
                pendingPreceding.push(node);
            } else if (currentTerm) {
                // Definition paragraphs, asides, etc. — belong to the term.
                currentTerm.content.push(node);
                currentTerm.haystack += ' ' + normalize(node.textContent);
            } else {
                // Orphan node before any term in the current letter — defer.
                pendingPreceding.push(node);
            }
        }
    };

    var setHidden = function (el, hidden) {
        if (!el) return;
        if (hidden) el.classList.add('is-hidden-by-filter');
        else el.classList.remove('is-hidden-by-filter');
    };

    var setNodesHidden = function (nodes, hidden) {
        if (!nodes) return;
        for (var i = 0; i < nodes.length; i++) setHidden(nodes[i], hidden);
    };

    var applyFilter = function (rawQuery) {
        var query = normalize((rawQuery || '').trim());
        var anyVisible = false;

        // Filter term groups (term heading + its preceding <hr> + content).
        for (var i = 0; i < termGroups.length; i++) {
            var g = termGroups[i];
            var match = (query === '') || (g.haystack.indexOf(query) !== -1);
            setHidden(g.heading, !match);
            setNodesHidden(g.preceding, !match);
            setNodesHidden(g.content, !match);
            if (match) anyVisible = true;
        }

        // Hide letter headings (+ their preceding <hr>) when all terms hide.
        for (var L = 0; L < letterGroups.length; L++) {
            var lg = letterGroups[L];
            var hasVisible = false;
            for (var t = 0; t < lg.terms.length; t++) {
                if (!lg.terms[t].heading.classList.contains('is-hidden-by-filter')) {
                    hasVisible = true;
                    break;
                }
            }
            setHidden(lg.heading, !hasVisible);
            setNodesHidden(lg.preceding, !hasVisible);
        }

        // Empty-state message.
        if ($empty) {
            if (anyVisible || query === '') {
                $empty.setAttribute('hidden', '');
            } else {
                $empty.removeAttribute('hidden');
            }
        }
    };

    var init = function () {
        $input = document.getElementById('glossary-search');
        if (!$input) return; // not on the glossaire page — sibling factsheets share this handler

        var root = document.querySelector('.factsheet-content .wrap .bd.glossary')
                || document.querySelector('.factsheet-content .wrap');
        if (!root) return;

        $empty = document.querySelector('.glossary-search_empty');

        buildGroups(root);

        $input.addEventListener('input', function () {
            applyFilter($input.value);
        });

        // If the URL carries a hash (e.g. /fiches-pratiques/glossaire#acompte),
        // a stale filter shouldn't hide the targeted term — fresh load starts empty.
    };

    init();

    return self;
})();
},window[originalContext]));
