/** @jsxRuntime classic */
/** @jsx React.createElement */
const { useEffect, useMemo, useState } = React;

(function ensureRulesTraceWriter() {
    window.__adRulesHooks = window.__adRulesHooks || [];
    if (!window.__adRulesWriterInstalled && window.ad && ad.log) {
        ad.log.addWriter(function (entry, rendered) {
            if (entry.level !== "debug") return;
            for (let i = 0; i < window.__adRulesHooks.length; i++) {
                try {
                    window.__adRulesHooks[i](rendered, entry);
                } catch (e) { }
            }
        });
        window.__adRulesWriterInstalled = true;
    }
})();

(function ensureUdlTraceWriter() {
    window.__udlDebugHooks = window.__udlDebugHooks || [];
    window.__installUdlDebugWriter = window.__installUdlDebugWriter || function () {
        if (window.__udlDebugWriterInstalled || !window.ad || !ad.log || typeof ad.log.addWriter !== "function") {
            return;
        }

        ad.log.addWriter(function (entry) {
            if (!entry || !entry.meta || entry.meta.source !== "udl") return;

            for (var i = 0; i < window.__udlDebugHooks.length; i++) {
                try {
                    window.__udlDebugHooks[i](entry);
                } catch (e) { }
            }
        });

        window.__udlDebugWriterInstalled = true;
    };
})();

window.__udlRuntimeHooks = window.__udlRuntimeHooks || [];
window.__installUdlRuntimeWriters = window.__installUdlRuntimeWriters || function () {
    window.dataLayer = window.dataLayer || [];

    if (!window.dataLayer.__runtimeHookWrapped) {
        var previousDataLayerPush = window.dataLayer.push;
        window.dataLayer.push = function () {
            var args = Array.prototype.slice.call(arguments);
            for (var i = 0; i < args.length; i++) {
                for (var j = 0; j < window.__udlRuntimeHooks.length; j++) {
                    try {
                        window.__udlRuntimeHooks[j]({
                            channel: "dataLayer",
                            payload: args[i],
                            tcVarsSnapshot: Object.assign({}, window.tc_vars || {}),
                            at: new Date().toISOString()
                        });
                    } catch (e) { }
                }
            }
            return previousDataLayerPush.apply(window.dataLayer, args);
        };
        window.dataLayer.__runtimeHookWrapped = true;
    }

    if (!window.cact || !window.cact.__runtimeHookWrapped) {
        var previousCact = window.cact || function () {
            (window.cact.q = window.cact.q || []).push(arguments);
        };

        var cactProxy = function () {
            var args = Array.prototype.slice.call(arguments);
            for (var i = 0; i < window.__udlRuntimeHooks.length; i++) {
                try {
                    window.__udlRuntimeHooks[i]({
                        channel: "cact",
                        payload: args,
                        tcVarsSnapshot: Object.assign({}, window.tc_vars || {}),
                        at: new Date().toISOString()
                    });
                } catch (e) { }
            }
            return previousCact.apply(window, args);
        };

        cactProxy.__runtimeHookWrapped = true;
        window.cact = cactProxy;
    }

    if (!window.__udlScriptObserverInstalled && typeof MutationObserver !== "undefined") {
        var target = document.head || document.documentElement;
        if (target) {
            var scriptObserver = new MutationObserver(function (mutations) {
                for (var mi = 0; mi < mutations.length; mi++) {
                    var added = mutations[mi].addedNodes || [];
                    for (var ai = 0; ai < added.length; ai++) {
                        var node = added[ai];
                        if (!node || node.tagName !== "SCRIPT") continue;

                        var src = node.src || node.getAttribute("src") || "inline-script";
                        var isTc = /tagcommander|tc_/i.test(src);
                        var isGtm = /googletagmanager|gtm/i.test(src);

                        for (var hi = 0; hi < window.__udlRuntimeHooks.length; hi++) {
                            try {
                                window.__udlRuntimeHooks[hi]({
                                    channel: "script",
                                    payload: {
                                        src: src,
                                        tmsType: isTc ? "TC" : (isGtm ? "GTM" : "other")
                                    },
                                    tcVarsSnapshot: Object.assign({}, window.tc_vars || {}),
                                    at: new Date().toISOString()
                                });
                            } catch (e) { }
                        }
                    }
                }
            });

            scriptObserver.observe(target, { childList: true, subtree: true });
            window.__udlScriptObserverInstalled = true;
        }
    }
};

window.__installUdlRuntimeWriters();

function pretty(value) {
    if (typeof value === "string") return value;
    try {
        return JSON.stringify(value, null, 2);
    } catch (e) {
        return String(value);
    }
}

function parseApiResponseSafe(res, endpointName) {
    return res.text().then(function (rawText) {
        var text = String(rawText || "");
        var contentType = String(res.headers.get("content-type") || "").toLowerCase();
        var parsed = null;

        if (text) {
            try {
                parsed = JSON.parse(text);
            } catch (e) {
                if (contentType.indexOf("application/json") !== -1) {
                    throw new Error("Reponse JSON invalide depuis " + endpointName + ".");
                }
            }
        }

        if (!res.ok) {
            if (parsed && parsed.error) {
                throw new Error(parsed.error);
            }
            if (/^\s*</.test(text)) {
                throw new Error("API " + endpointName + " indisponible (reponse HTML). Verifiez que le serveur est redemarre avec la derniere version.");
            }
            throw new Error("HTTP " + res.status + " sur " + endpointName + ".");
        }

        if (!parsed) {
            throw new Error("Reponse inattendue depuis " + endpointName + ".");
        }

        return parsed;
    });
}

const EXAMPLE_VIP = {
    perimeter: {
        product: [
            {
                _description: "Client VIP sur panier > 100",
                and: [{ "==": ["user.segment", "vip"] }, { ">": ["cart.total", 100] }, "promo_vip"]
            },
            {
                _description: "Client standard",
                "==": ["user.segment", "standard", "promo_std"]
            }
        ]
    },
    context: {
        user: { segment: "vip", country: "FR" },
        cart: { total: 129 },
        segments: ["vip", "newsletter"]
    }
};

const EXAMPLE_LOOKUP = {
    perimeter: {
        offer: [
            {
                _description: "Mapping segment -> offre",
                lookup: [
                    "user.segment",
                    { vip: "offer_gold", standard: "offer_silver", guest: "offer_discovery" },
                    "offer_default"
                ]
            }
        ]
    },
    context: {
        user: { segment: "vip" }
    }
};

const LOG_SNIPPET = [
    'ad.log.setLevel("debug");',
    'ad.log.info("RulesLab start", { screen: "rules" });',
    'ad.log.setFormatter(function (entry) {',
    '  return "[" + entry.level.toUpperCase() + "] " + entry.args.join(" ");',
    '});',
    'ad.log.warn("Formatter switched");'
].join("\n");

const RULES_STRUCTURE_SNIPPET = [
    '{',
    '  "product": [',
    '    { "==": ["user.segment", "vip", "promo_vip"] },',
    '    { "and": [',
    '        { ">": ["cart.total", 100] },',
    '        { "in": ["user.country", ["FR", "BE"]] },',
    '        { "$return": "promo_eu" }',
    '      ]',
    '    },',
    '    { "or": [',
    '        { "==": ["user.segment", "vip"] },',
    '        { "and": [',
    '            { "==": ["user.segment", "standard"] },',
    '            { ">": ["cart.total", 80] },',
    '            { "$return": true }',
    '          ]',
    '        },',
    '        { "$return": "eligible_offer" }',
    '      ]',
    '    }',
    '  ]',
    '  // Variante IN variadique:',
    '  // { "in": ["user.segment", "vip", "standard", { "$return": "segment_known" }] }',
    '}'
].join("\n");

const RULES_EDITOR_SAMPLE_DATA = {
    user: {
        segment: "vip",
        country: "FR",
        age: 34
    },
    cart: {
        total: 129,
        currency: "EUR",
        items: [
            { sku: "A1", category: "energy" },
            { sku: "B2", category: "services" }
        ]
    },
    page: {
        type: "landing",
        channel: "web"
    }
};

const BACKOFFICE_HELP_SCREENS = [
    {
        id: "rulesEditor",
        label: "Rules Editor",
        description: "Nouvel ecran d'edition de regles arborescentes appliquees a une structure de donnees JSON.",
        objectFocus: "Un objet de donnees source + un arbre de regles recursif (groupes AND/OR + conditions).",
        whenToUse: [
            "quand vous devez modeliser des regles complexes imbriquees",
            "quand vous voulez separer clairement la donnee d'entree et la logique de decision",
            "quand vous devez visualiser la recurrence de regles via une arborescence"
        ],
        steps: [
            "charger ou coller une structure JSON de reference",
            "composer l'arbre de regles avec des groupes et des conditions",
            "appliquer les regles et lire le resultat + la trace"
        ],
        fields: [
            {
                name: "Structure JSON",
                description: "Donnee de reference sur laquelle les regles sont evaluees.",
                expected: "objet JSON valide",
                example: "{ user: { segment: 'vip' }, cart: { total: 129 } }"
            },
            {
                name: "Noeuds de regles",
                description: "Groupes (all/any) et conditions unitaires recursives.",
                expected: "arbre coherent avec chemins de donnees",
                example: "group all -> condition user.segment == vip + group any"
            }
        ],
        actions: [
            {
                name: "Charger structure",
                description: "Valide et memorise la structure JSON de travail.",
                outcome: "active l'etape d'edition de regles"
            },
            {
                name: "Ajouter groupe / condition",
                description: "Insere de nouveaux noeuds dans l'arbre de regles.",
                outcome: "permet de modeliser des regles recurrentes"
            },
            {
                name: "Appliquer les regles",
                description: "Evalue l'arbre sur la structure chargee.",
                outcome: "affiche resultat booleen + journal de decision"
            }
        ],
        outputs: [
            "Resultat d'evaluation (true/false)",
            "Trace de chaque noeud evalue",
            "JSON de l'arbre de regles"
        ],
        takeaway: "Cet ecran installe une base d'editeur de regles arborescent evolutif, decouple du runtime UDL."
    },
    {
        id: "udlSource",
        label: "UDL Source Editor",
        description: "Ecran d'edition distante des sources JavaScript UDL autorisees par le serveur.",
        objectFocus: "Fichiers source UDL editables (udl, ad_lib) avec rechargement et sauvegarde versionnee par fichier.",
        whenToUse: [
            "quand vous devez corriger rapidement un fichier UDL sans quitter le backoffice",
            "quand vous voulez comparer plusieurs sources avant packaging",
            "quand vous devez relire puis sauvegarder un patch local de runtime"
        ],
        steps: [
            "selectionner un fichier dans la liste autorisee",
            "editer le code JavaScript dans l'editeur",
            "saisir auteur + motif obligatoire puis sauvegarder",
            "consulter l'historique et restaurer une revision si necessaire"
        ],
        fields: [
            {
                name: "Source a editer",
                description: "Liste des fichiers explicitement autorises cote serveur.",
                expected: "un identifiant de source UDL",
                example: "Runtime principal (udl.js)"
            },
            {
                name: "Code JavaScript",
                description: "Contenu texte du fichier selectionne.",
                expected: "JavaScript valide",
                example: "ajout d'un log de debug ou correction d'une condition"
            },
            {
                name: "Auteur + motif",
                description: "Informations de traçabilite de chaque modification.",
                expected: "auteur texte + motif non vide",
                example: "jdoe | corrige mapping event checkout"
            }
        ],
        actions: [
            {
                name: "Rafraichir la liste",
                description: "Recharge la liste des sources disponibles.",
                outcome: "met a jour taille/date des fichiers"
            },
            {
                name: "Recharger le fichier",
                description: "Recharge depuis disque le fichier actif.",
                outcome: "annule les modifications non sauvegardees"
            },
            {
                name: "Sauvegarder",
                description: "Ecrit le contenu courant dans le fichier cible avec trace obligatoire.",
                outcome: "cree une revision historisee avec sha avant/apres"
            },
            {
                name: "Restaurer une revision",
                description: "Reapplique une revision precedente dans le fichier courant.",
                outcome: "cree une nouvelle revision de type restore"
            },
            {
                name: "Comparer deux revisions",
                description: "Affiche un diff ligne a ligne entre deux revisions historisees.",
                outcome: "met en evidence ajouts/suppressions/modifications"
            }
        ],
        outputs: [
            "Etat de sauvegarde (ok/erreur)",
            "Metadata fichier (taille, date)",
            "Empreinte sha256 de la version ecrite",
            "Historique des revisions (acteur, motif, action, hashes)"
        ],
        takeaway: "Cet ecran couvre la phase 1 d'un backoffice UDL orientee edition de sources avec traçabilite forte des changements."
    },
    {
        id: "udl",
        label: "UDL Config Debug",
        description: "Ecran d'edition de la configuration udl_config.js, avec profils, defaults et regles de selection par host ou href.",
        objectFocus: "L'objet udlConfig complet: defaults, profiles, rules et containers. C'est la source de decision du runtime UDL.",
        whenToUse: [
            "quand vous devez ajouter ou corriger un profil de site",
            "quand vous devez changer un container GTM ou TC",
            "quand vous voulez comprendre pourquoi un host charge un profil plutot qu'un autre"
        ],
        steps: [
            "editer la configuration via le formulaire structure ou la vue JSON",
            "tester un host ou une URL pour voir quel profil serait retenu",
            "verifier la synthese des containers et le journal de resolution",
            "revenir a window.udlConfig si vous voulez repartir de l'etat courant"
        ],
        fields: [
            {
                name: "Profils",
                description: "Chaque profil represente une configuration cible possible pour un domaine ou une URL.",
                expected: "nom de profil + internalDomains + containers",
                example: "edfPartProd avec un container TC particulier"
            },
            {
                name: "Regles de selection de profil",
                description: "Associe un host ou un href a un profil via egalite ou regex.",
                expected: "valeur testee + type de regle + profil cible",
                example: "host match localhost$ -> edfPartUat"
            },
            {
                name: "Vue JSON",
                description: "Affiche et permet d'editer l'objet udlConfig complet.",
                expected: "JSON valide avec defaults, profiles et rules",
                example: "ajout d'un mapping tcEventMap ou d'un nouveau container"
            }
        ],
        actions: [
            {
                name: "Appliquer le formulaire / Appliquer JSON",
                description: "Recharge la configuration de travail locale du lab.",
                outcome: "les tests de profil utilisent immédiatement cette nouvelle config"
            },
            {
                name: "Tester / Tester toutes les regles en egalite",
                description: "Simule la resolution de profil pour un host ou plusieurs cas.",
                outcome: "alimente la synthese multi-cas et le journal de resolution"
            },
            {
                name: "Reinitialiser",
                description: "Revient a l'etat actuel de window.udlConfig charge dans la page.",
                outcome: "efface vos modifications locales non appliquees au code source"
            }
        ],
        outputs: [
            "Profil selectionne et containers resolves",
            "Journal de resolution indiquant pourquoi une regle matche ou non",
            "Synthese multi-cas pour comparer plusieurs hosts rapidement"
        ],
        takeaway: "Cet ecran explique comment le runtime choisit sa configuration avant meme de pousser des evenements dans dataLayer."
    },
    {
        id: "udlVersions",
        label: "UDL Package",
        description: "Ecran de versioning udl_config.js et de generation de packages a partir de versions selectionnees.",
        objectFocus: "Version udl_config + revisions de udl/ad_lib assemblees dans un package reproductible.",
        whenToUse: [
            "quand vous voulez historiser des variantes de udl_config.js",
            "quand vous devez livrer un package compose de versions precises",
            "quand vous voulez transformer une config issue de UDL Config Debug en package consommable"
        ],
        steps: [
            "importer la config depuis UDL Config Debug ou window.udlConfig",
            "enregistrer un snapshot versionne",
            "selectionner les versions udl/ad_lib/udl_config puis generer un package (bundle, autonome, ou les deux)"
        ],
        fields: [
            {
                name: "Configuration JSON",
                description: "Contenu brut qui sera converti en udl_config.js versionne.",
                expected: "objet JSON valide",
                example: "{ \"defaults\": {...}, \"profiles\": {...}, \"rules\": {...} }"
            },
            {
                name: "Label de version",
                description: "Description courte du contexte fonctionnel de la version.",
                expected: "texte libre",
                example: "maj regex souscription juillet"
            },
            {
                name: "Versions de package",
                description: "Choix des versions de udl, ad_lib et udl_config a assembler.",
                expected: "format index | date | auteur | libelle",
                example: "#12 | 2026-07-04T06:12:00.000Z | jdoe | fix domain regex"
            },
            {
                name: "Mode de package",
                description: "Choix des artefacts de sortie a generer.",
                expected: "bundle, autonomous ou both",
                example: "autonomous pour udl_uat.js + ad_lib.js + udl_config.js"
            }
        ],
        actions: [
            {
                name: "Enregistrer une nouvelle version",
                description: "Cree un snapshot persistant avec ID et metadata.",
                outcome: "la version apparait dans la liste et peut etre packagee"
            },
            {
                name: "Generer le package",
                description: "Assemble les scripts runtime avec les versions selectionnees puis cree un ZIP.",
                outcome: "un dossier + un zip telechargeable sont disponibles dans dist/versioned"
            }
        ],
        outputs: [
            "URLs d'integration du build courant (udl.js, udl_uat.js, ad_lib.js, udl_config.js)",
            "Liste des versions disponibles",
            "Chemin du package genere et manifest",
            "Lien de telechargement du zip"
        ],
        takeaway: "Cet ecran relie la phase d'edition de configuration a une livraison versionnee et reproductible. Chaque package genere cree aussi automatiquement un snapshot des fichiers UDL critiques, consultable et restaurable directement dans cet ecran (etapes 3 et 4)."
    },
    {
        id: "udlRuntime",
        label: "UDL Runtime Lab",
        description: "Ecran de simulation bout en bout du runtime: chargement des scripts, selection de profil, start UDL et injection d'evenements integrateur.",
        objectFocus: "Le trio runtime UDL + dependances + objet pousse dans dataLayer. Ici on observe ce qui part vers tc_vars, cact et les scripts TMS charges.",
        whenToUse: [
            "quand vous voulez reproduire le comportement d'integration d'un site",
            "quand vous devez comparer le runtime prod et UAT",
            "quand vous voulez voir les effets d'un payload dataLayer sur les sorties UDL"
        ],
        steps: [
            "charger le runtime voulu",
            "simuler le domaine cible puis executer Dry run et Initialiser",
            "pousser un objet integrateur et lire les traces de dataLayer, tc_vars et cact"
        ],
        fields: [
            {
                name: "Runtime UDL",
                description: "Choisit le script charge pour le test.",
                expected: "udl.js pour le bundle prod, udl_uat.js pour la version lisible",
                example: "udl_uat.js si vous voulez inspecter le chargement des dependances"
            },
            {
                name: "Host simule / URL simulee",
                description: "Definit le contexte dans lequel le profil UDL est resolu.",
                expected: "un domaine cible et, si necessaire, une URL complete",
                example: "host=localhost, href=http://localhost:3001/"
            },
            {
                name: "udl_config.js (JSON)",
                description: "Source editee localement dans l'onglet Editer udl_config.js, utilisee directement par Dry run et Initialiser.",
                expected: "objet JavaScript valide et evaluable (window.udlConfig = {...})",
                example: "screen_view -> pageview dans tcEventMap"
            },
            {
                name: "Template objet integrateur",
                description: "Preselectionne un payload type a pousser dans dataLayer.",
                expected: "un cas fonctionnel courant a partir duquel partir",
                example: "Chargement page (screen_view)"
            },
            {
                name: "Objet pousse dans dataLayer (JSON)",
                description: "Payload exact simule cote integrateur.",
                expected: "JSON valide, generalement avec un champ event",
                example: "{ \"event\": \"screen_view\", \"screen_name\": \"home\" }"
            }
        ],
        actions: [
            {
                name: "Charger runtime UDL",
                description: "Charge le fichier runtime selectionne sans lancer `start()`.",
                outcome: "window.udl devient disponible dans la page"
            },
            {
                name: "Dry run",
                description: "Teste la selection de profil sans demarrer le runtime.",
                outcome: "affiche le profil retenu et les containers associes"
            },
            {
                name: "Initialiser pour le domaine simule",
                description: "Lance le runtime avec la configuration resolue pour le domaine saisi.",
                outcome: "patch dataLayer, charge les containers et active les traces runtime"
            },
            {
                name: "Recharger depuis le fichier distant / Annuler / Rejouer",
                description: "Pilote l'editeur udl_config.js local du lab.",
                outcome: "charge la source courante, annule ou rejoue les modifications de l'editeur"
            },
            {
                name: "Pousser dans dataLayer",
                description: "Simule l'integrateur en envoyant le JSON saisi dans dataLayer.",
                outcome: "UDL mappe l'evenement, nourrit tc_vars et peut appeler cact"
            }
        ],
        outputs: [
            "Traces des donnees envoyees aux TMS: dataLayer, scripts TMS et emissions cact",
            "Journal technique: sequence d'actions utilisateur + logs internes UDL",
            "Badges d'etat: dependances chargees ou non, runtime charge ou non"
        ],
        takeaway: "Cet ecran est le plus proche d'un vrai parcours d'integration, avec une vision complete des entrees et des sorties."
    }
];

function RulesEditor() {
    const [dataText, setDataText] = useState(pretty(RULES_EDITOR_SAMPLE_DATA));
    const [dataObject, setDataObject] = useState(deepClone(RULES_EDITOR_SAMPLE_DATA));
    const [status, setStatus] = useState("Structure chargee.");
    const [statusType, setStatusType] = useState("ok");
    const [evaluation, setEvaluation] = useState(null);
    const [rulesReady, setRulesReady] = useState(!!(window.ad && window.ad.rules));
    const [rulesLoading, setRulesLoading] = useState(false);
    const [draggedNodeId, setDraggedNodeId] = useState("");
    const [dragOverGroupId, setDragOverGroupId] = useState("");

    const [ruleTree, setRuleTree] = useState({
        id: "root",
        type: "group",
        operator: "and",
        returnValue: "",
        children: [
            {
                id: "cond-1",
                type: "condition",
                path: "user.segment",
                operator: "==",
                value: "vip",
                returnValue: ""
            }
        ]
    });

    const pathSuggestions = useMemo(function () {
        function walk(obj, prefix, out) {
            if (obj == null) return;

            if (Array.isArray(obj)) {
                if (prefix) out[prefix] = true;
                if (obj.length > 0) {
                    walk(obj[0], prefix ? (prefix + ".0") : "0", out);
                }
                return;
            }

            if (typeof obj !== "object") {
                if (prefix) out[prefix] = true;
                return;
            }

            var keys = Object.keys(obj);
            for (var i = 0; i < keys.length; i++) {
                var key = keys[i];
                var nextPrefix = prefix ? (prefix + "." + key) : key;
                out[nextPrefix] = true;
                walk(obj[key], nextPrefix, out);
            }
        }

        var bag = {};
        walk(dataObject, "", bag);
        return Object.keys(bag).sort();
    }, [dataObject]);

    function newId(prefix) {
        return prefix + "-" + Date.now() + "-" + Math.floor(Math.random() * 1000);
    }

    function getValueByPath(obj, path) {
        var tokens = String(path || "").split(".").map(function (t) { return t.trim(); }).filter(Boolean);
        var cursor = obj;
        for (var i = 0; i < tokens.length; i++) {
            if (cursor == null || typeof cursor !== "object" || !(tokens[i] in cursor)) return undefined;
            cursor = cursor[tokens[i]];
        }
        return cursor;
    }

    function updateNodeById(node, nodeId, updater) {
        if (!node) return node;
        if (node.id === nodeId) return updater(deepClone(node));
        if (node.type !== "group" || !Array.isArray(node.children)) return node;

        return Object.assign({}, node, {
            children: node.children.map(function (child) {
                return updateNodeById(child, nodeId, updater);
            })
        });
    }

    function removeNodeById(node, nodeId) {
        if (!node || node.type !== "group" || !Array.isArray(node.children)) return node;

        return Object.assign({}, node, {
            children: node.children
                .filter(function (child) { return child.id !== nodeId; })
                .map(function (child) { return removeNodeById(child, nodeId); })
        });
    }

    function addChildNode(parentId, childType) {
        setRuleTree(function (prev) {
            return updateNodeById(prev, parentId, function (parent) {
                if (parent.type !== "group") return parent;

                var nextChild = childType === "group"
                    ? { id: newId("group"), type: "group", operator: "and", returnValue: "", children: [] }
                    : { id: newId("cond"), type: "condition", path: "", operator: "==", value: "", returnValue: "" };

                parent.children = Array.isArray(parent.children) ? parent.children.concat([nextChild]) : [nextChild];
                return parent;
            });
        });
    }

    function updateGroupOperator(groupId, operator) {
        setRuleTree(function (prev) {
            return updateNodeById(prev, groupId, function (group) {
                if (group.type === "group") group.operator = operator;
                return group;
            });
        });
    }

    function updateConditionField(conditionId, field, value) {
        setRuleTree(function (prev) {
            return updateNodeById(prev, conditionId, function (condition) {
                if (condition.type === "condition") condition[field] = value;
                return condition;
            });
        });
    }

    function removeNode(nodeId) {
        if (nodeId === "root") return;
        setRuleTree(function (prev) { return removeNodeById(prev, nodeId); });
    }

    function findNodeById(node, nodeId) {
        if (!node) return null;
        if (node.id === nodeId) return node;
        if (node.type !== "group" || !Array.isArray(node.children)) return null;

        for (var i = 0; i < node.children.length; i++) {
            var found = findNodeById(node.children[i], nodeId);
            if (found) return found;
        }
        return null;
    }

    function isDescendant(parentNode, candidateChildId) {
        if (!parentNode || parentNode.type !== "group") return false;
        var children = parentNode.children || [];
        for (var i = 0; i < children.length; i++) {
            var child = children[i];
            if (child.id === candidateChildId) return true;
            if (child.type === "group" && isDescendant(child, candidateChildId)) return true;
        }
        return false;
    }

    function detachNode(root, nodeId) {
        if (!root || root.id === nodeId) {
            return { tree: root, removed: null };
        }

        if (root.type !== "group" || !Array.isArray(root.children)) {
            return { tree: root, removed: null };
        }

        var removed = null;
        var nextChildren = [];

        for (var i = 0; i < root.children.length; i++) {
            var child = root.children[i];
            if (child.id === nodeId) {
                removed = deepClone(child);
                continue;
            }

            var detached = detachNode(child, nodeId);
            if (detached.removed && !removed) {
                removed = detached.removed;
            }
            nextChildren.push(detached.tree);
        }

        return {
            tree: Object.assign({}, root, { children: nextChildren }),
            removed: removed
        };
    }

    function insertNodeAsChild(root, groupId, nodeToInsert) {
        return updateNodeById(root, groupId, function (group) {
            if (group.type !== "group") return group;
            var nextChildren = Array.isArray(group.children) ? group.children.slice() : [];
            nextChildren.push(nodeToInsert);
            group.children = nextChildren;
            return group;
        });
    }

    function onNodeDragStart(event, nodeId) {
        setDraggedNodeId(nodeId);
        event.dataTransfer.effectAllowed = "move";
        try {
            event.dataTransfer.setData("text/plain", nodeId);
        } catch (e) { }
    }

    function onDropOnGroup(event, groupId) {
        event.preventDefault();
        event.stopPropagation();

        if (!draggedNodeId || draggedNodeId === "root" || draggedNodeId === groupId) {
            setDraggedNodeId("");
            setDragOverGroupId("");
            return;
        }

        setRuleTree(function (prev) {
            var targetGroup = findNodeById(prev, groupId);
            var movingNode = findNodeById(prev, draggedNodeId);
            if (!targetGroup || targetGroup.type !== "group" || !movingNode) return prev;
            if (movingNode.type === "group" && isDescendant(movingNode, groupId)) return prev;

            var detached = detachNode(prev, draggedNodeId);
            if (!detached.removed) return prev;

            return insertNodeAsChild(detached.tree, groupId, detached.removed);
        });

        setDraggedNodeId("");
        setDragOverGroupId("");
        setStatusType("ok");
        setStatus("Noeud deplace dans le groupe cible.");
    }

    function loadDataStructure() {
        try {
            var parsed = JSON.parse(dataText);
            setDataObject(parsed);
            setStatusType("ok");
            setStatus("Structure chargee et validee.");
        } catch (e) {
            setStatusType("error");
            setStatus("JSON invalide: " + e.message);
        }
    }

    function ensureRulesEngineReady() {
        if (window.ad && window.ad.rules) {
            setRulesReady(true);
            return Promise.resolve();
        }

        setRulesLoading(true);

        return new Promise(function (resolve, reject) {
            var src = "js/ad_lib.js";
            var existing = document.querySelector('script[src^="' + src + '"]');

            function finalize() {
                if (window.ad && window.ad.rules) {
                    setRulesReady(true);
                    setRulesLoading(false);
                    resolve();
                } else {
                    setRulesLoading(false);
                    reject(new Error("ad.rules indisponible apres chargement de ad_lib.js"));
                }
            }

            if (existing) {
                finalize();
                return;
            }

            var script = document.createElement("script");
            script.src = src;
            script.async = false;
            script.onload = finalize;
            script.onerror = function () {
                setRulesLoading(false);
                reject(new Error("Impossible de charger " + src));
            };
            (document.head || document.documentElement).appendChild(script);
        });
    }

    useEffect(function () {
        ensureRulesEngineReady().catch(function () {
            // L'utilisateur peut continuer a travailler puis relancer plus tard.
        });
    }, []);

    function parseEditorScalar(raw) {
        var source = String(raw == null ? "" : raw).trim();
        if (!source) return "";
        try {
            return JSON.parse(source);
        } catch (e) { }
        return source;
    }

    function parseInValues(raw) {
        var source = String(raw == null ? "" : raw).trim();
        if (!source) return [];

        if (source[0] === "[") {
            try {
                var parsed = JSON.parse(source);
                if (Array.isArray(parsed)) return parsed;
            } catch (e) { }
        }

        return source.split(",").map(function (part) {
            return parseEditorScalar(part);
        }).filter(function (v) {
            return String(v).trim() !== "";
        });
    }

    function toAdRule(node) {
        if (!node) return null;

        if (node.type === "group") {
            var op = (node.operator === "or" || node.operator === "not") ? node.operator : "and";
            var children = (node.children || []).map(toAdRule).filter(Boolean);
            var groupReturn = String(node.returnValue || "").trim();

            if (!children.length) return { and: [] };

            if (op === "not") {
                return { not: [children[0]] };
            }

            if (groupReturn) {
                children.push({ $return: parseEditorScalar(groupReturn) });
            }

            var grouped = {};
            grouped[op] = children;
            return grouped;
        }

        var params = [];
        var path = String(node.path || "").trim();
        if (!path) return null;
        params.push(path);

        if (node.operator === "in") {
            params.push(parseInValues(node.value));
        } else if (node.operator !== "var") {
            params.push(parseEditorScalar(node.value));
        }

        var rv = String(node.returnValue || "").trim();
        if (rv) {
            params.push(parseEditorScalar(rv));
        }

        var logic = {};
        logic[node.operator || "=="] = params;
        return logic;
    }

    function runEvaluation() {
        if (!dataObject) {
            setStatusType("error");
            setStatus("Chargez d'abord une structure JSON valide.");
            return;
        }

        ensureRulesEngineReady().then(function () {
            var logic = toAdRule(ruleTree);
            if (!logic) {
                setStatusType("error");
                setStatus("Arbre de regles invalide.");
                return;
            }

            var passed = false;
            var applied;
            var trace = [];

            function evalNodeDetailed(node, depth) {
                var lvl = depth || 0;
                var pad = new Array(lvl * 2 + 1).join(" ");
                var nodeRule = toAdRule(node);
                if (!nodeRule) {
                    trace.push(pad + "- NOEUD invalide");
                    return false;
                }

                if (node.type === "condition") {
                    var conditionTest = !!ad.rules.test(nodeRule, dataObject);
                    var conditionApply = ad.rules.apply(nodeRule, dataObject);
                    var rvInfo = String(node.returnValue || "").trim() ? (" | return=" + pretty(parseEditorScalar(node.returnValue))) : "";
                    trace.push(pad + "- CONDITION " + (node.path || "(path vide)") + " " + (node.operator || "==") + " " + (node.value || "") + " => " + (conditionTest ? "TRUE" : "FALSE") + rvInfo);
                    if (conditionTest) {
                        trace.push(pad + "  apply => " + (typeof conditionApply === "undefined" ? "undefined" : pretty(conditionApply)));
                    }
                    return conditionTest;
                }

                var op = node.operator || "and";
                var children = Array.isArray(node.children) ? node.children : [];
                var groupReturnRaw = String(node.returnValue || "").trim();
                var groupReturnParsed = groupReturnRaw ? parseEditorScalar(groupReturnRaw) : undefined;
                trace.push(pad + "- GROUPE " + op.toUpperCase() + " (" + children.length + " enfant(s))");

                if (!children.length) {
                    var emptyTest = !!ad.rules.test(nodeRule, dataObject);
                    var emptyApply = ad.rules.apply(nodeRule, dataObject);
                    trace.push(pad + "  groupe vide => " + (emptyTest ? "TRUE" : "FALSE") + " | apply=" + (typeof emptyApply === "undefined" ? "undefined" : pretty(emptyApply)));
                    return emptyTest;
                }

                if (op === "not") {
                    var notChildResult = evalNodeDetailed(children[0], lvl + 1);
                    var notResult = !notChildResult;
                    var notApply = ad.rules.apply(nodeRule, dataObject);
                    trace.push(pad + "  NOT inverse " + (notChildResult ? "TRUE" : "FALSE") + " -> " + (notResult ? "TRUE" : "FALSE") + " | apply=" + (typeof notApply === "undefined" ? "undefined" : pretty(notApply)));
                    return notResult;
                }

                var aggregate = (op === "or") ? false : true;
                for (var i = 0; i < children.length; i++) {
                    var childResult = evalNodeDetailed(children[i], lvl + 1);

                    if (op === "and" && !childResult) {
                        aggregate = false;
                        trace.push(pad + "  AND stop: enfant " + (i + 1) + " est FALSE");
                        break;
                    }

                    if (op === "or" && childResult) {
                        aggregate = true;
                        trace.push(pad + "  OR stop: enfant " + (i + 1) + " est TRUE");
                        break;
                    }

                    if (op === "or") aggregate = false;
                }

                var groupApply = ad.rules.apply(nodeRule, dataObject);
                trace.push(pad + "  GROUPE " + op.toUpperCase() + " => " + (aggregate ? "TRUE" : "FALSE") + " | apply=" + (typeof groupApply === "undefined" ? "undefined" : pretty(groupApply)));
                if (groupReturnRaw) {
                    if (aggregate) {
                        trace.push(pad + "  $return groupe utilise => " + pretty(groupReturnParsed));
                    } else {
                        trace.push(pad + "  $return groupe defini mais non utilise (groupe non match)");
                    }
                }
                return aggregate;
            }

            try {
                passed = evalNodeDetailed(ruleTree, 0);
                applied = ad.rules.apply(logic, dataObject);
            } catch (e) {
                setStatusType("error");
                setStatus("Erreur moteur: " + e.message);
                return;
            }

            trace.push("RESULTAT FINAL test => " + (passed ? "TRUE" : "FALSE"));
            trace.push("RESULTAT FINAL apply => " + (typeof applied === "undefined" ? "undefined" : pretty(applied)));

            setEvaluation({
                passed: passed,
                applied: typeof applied === "undefined" ? null : applied,
                trace: trace,
                logic: logic
            });
            setStatusType("ok");
            setStatus("Evaluation ad.rules terminee.");
        }).catch(function (e) {
            setRulesReady(false);
            setStatusType("error");
            setStatus(e && e.message ? e.message : "ad.rules indisponible sur la page.");
        });
    }

    function resetToExample() {
        setDataText(pretty(RULES_EDITOR_SAMPLE_DATA));
        setDataObject(deepClone(RULES_EDITOR_SAMPLE_DATA));
        setRuleTree({
            id: "root",
            type: "group",
            operator: "and",
            returnValue: "",
            children: [
                {
                    id: "cond-1",
                    type: "condition",
                    path: "user.segment",
                    operator: "==",
                    value: "vip",
                    returnValue: ""
                }
            ]
        });
        setEvaluation(null);
        setStatusType("ok");
        setStatus("Exemple recharge.");
    }

    function renderRuleNode(node, depth) {
        var level = depth || 0;

        if (node.type === "group") {
            return (
                <div
                    key={node.id}
                    className={"rule-node" + (dragOverGroupId === node.id ? " drag-over" : "")}
                    style={{ marginLeft: (level * 18) + "px" }}
                    onDragOver={function (e) { e.preventDefault(); setDragOverGroupId(node.id); }}
                    onDragLeave={function () { if (dragOverGroupId === node.id) setDragOverGroupId(""); }}
                    onDrop={function (e) { onDropOnGroup(e, node.id); }}
                >
                    <div className="rule-node-head rule-node-head-group">
                        <span
                            className={"rule-node-badge" + (node.id !== "root" ? " draggable-handle" : "")}
                            draggable={node.id !== "root"}
                            onDragStart={node.id !== "root" ? function (e) { onNodeDragStart(e, node.id); } : undefined}
                            onDragEnd={node.id !== "root" ? function () { setDraggedNodeId(""); setDragOverGroupId(""); } : undefined}
                            title={node.id !== "root" ? "Deplacer ce groupe" : "Groupe racine"}
                        >
                            Groupe
                        </span>
                        <label className="label" style={{ marginBottom: 0 }}>Operateur</label>
                        <select value={node.operator} onChange={function (e) { updateGroupOperator(node.id, e.target.value); }} style={{ width: "auto" }}>
                            <option value="and">and</option>
                            <option value="or">or</option>
                            <option value="not">not (1er enfant seulement)</option>
                        </select>
                        <input
                            value={node.returnValue || ""}
                            placeholder={node.operator === "not" ? "retour non supporte pour not" : "valeur de retour groupe (optionnelle)"}
                            onChange={function (e) { setRuleTree(function (prev) { return updateNodeById(prev, node.id, function (group) { group.returnValue = e.target.value; return group; }); }); }}
                            disabled={node.operator === "not"}
                        />
                        <button type="button" className="btn secondary" onClick={function () { addChildNode(node.id, "condition"); }}>+ Condition</button>
                        <button type="button" className="btn secondary" onClick={function () { addChildNode(node.id, "group"); }}>+ Groupe enfant</button>
                        {node.id !== "root" && <button type="button" className="btn secondary" onClick={function () { removeNode(node.id); }}>Supprimer</button>}
                    </div>
                    <div className="rule-node-children">
                        {(node.children || []).map(function (child) {
                            return renderRuleNode(child, level + 1);
                        })}
                    </div>
                </div>
            );
        }

        return (
            <div key={node.id} className="rule-node rule-node-condition" style={{ marginLeft: (level * 18) + "px" }}>
                <div className="rule-node-head rule-node-head-condition">
                    <span
                        className="rule-node-badge draggable-handle"
                        draggable
                        onDragStart={function (e) { onNodeDragStart(e, node.id); }}
                        onDragEnd={function () { setDraggedNodeId(""); setDragOverGroupId(""); }}
                        title="Deplacer cette condition"
                    >
                        Condition
                    </span>
                    <input
                        value={node.path || ""}
                        placeholder="variable a tester (ex: user.segment)"
                        list="rules-editor-path-suggestions"
                        onChange={function (e) { updateConditionField(node.id, "path", e.target.value); }}
                    />
                    <select value={node.operator || "=="} onChange={function (e) { updateConditionField(node.id, "operator", e.target.value); }} style={{ width: "auto" }}>
                        <option value="==">==</option>
                        <option value="===">===</option>
                        <option value=">">&gt;</option>
                        <option value="<">&lt;</option>
                        <option value="match">match</option>
                        <option value="in">in</option>
                        <option value="var">var</option>
                    </select>
                    <input
                        value={node.value || ""}
                        placeholder={node.operator === "in" ? "values (JSON array ou CSV)" : "value (texte libre accepte, sans guillemets)"}
                        onChange={function (e) { updateConditionField(node.id, "value", e.target.value); }}
                        disabled={node.operator === "var"}
                    />
                    <input
                        value={node.returnValue || ""}
                        placeholder="3e valeur optionnelle = valeur de retour"
                        onChange={function (e) { updateConditionField(node.id, "returnValue", e.target.value); }}
                    />
                    <button type="button" className="btn secondary" onClick={function () { removeNode(node.id); }}>Supprimer</button>
                </div>
                <div className="field-hint" style={{ marginTop: "6px" }}>
                    {node.operator === "==" ? "== compare avec conversion de type (coercion)." : null}
                    {node.operator === "===" ? "=== compare sans conversion (type et valeur stricts)." : null}
                    {(node.operator !== "==" && node.operator !== "===") ? "Ajoutez une valeur de retour pour forcer apply(...) a renvoyer cette valeur en cas de match." : null}
                </div>
            </div>
        );
    }

    return (
        <>
            <div className="content-header">
                <h1 className="title">Rules Editor</h1>
                <p className="subtitle">Editeur arborescent ad.rules natif avec auto-completion des chemins de variables bases sur la structure JSON chargee.</p>
            </div>

            <datalist id="rules-editor-path-suggestions">
                {pathSuggestions.map(function (path) {
                    return <option key={path} value={path} />;
                })}
            </datalist>

            <section className="panel runtime-step-panel" style={{ marginBottom: "16px" }}>
                <div className="panel-kicker">Etape 1</div>
                <h3>Charger une structure de donnees</h3>
                <div className="field" style={{ marginTop: "8px" }}>
                    <label className="label">Structure JSON</label>
                    <CodeEditor language="json" value={dataText} onChange={setDataText} height={260} />
                </div>
                <div className="actions">
                    <button type="button" className="btn" onClick={loadDataStructure}>Charger la structure</button>
                    <button type="button" className="btn secondary" onClick={resetToExample}>Recharger un exemple</button>
                </div>
                <div className={"status " + statusType}>{status}</div>
                <div className={"status " + (rulesReady ? "ok" : (rulesLoading ? "ok" : "error"))}>
                    ad.rules: {rulesReady ? "disponible" : (rulesLoading ? "chargement..." : "non charge")}
                </div>
            </section>

            <section className="panel runtime-step-panel" style={{ marginBottom: "16px" }}>
                <div className="panel-kicker">Etape 2</div>
                <h3>Editer des regles arborescentes</h3>
                <p className="subtitle compact">L'evaluation passe directement par ad.rules.test/apply avec le JSON de regle genere.</p>

                <div className="rule-tree-root">
                    {renderRuleNode(ruleTree, 0)}
                </div>

                <div className="actions">
                    <button type="button" className="btn" onClick={runEvaluation}>Appliquer les regles a la structure</button>
                </div>

                {evaluation && (
                    <div className="udl-manager-grid" style={{ marginTop: "12px" }}>
                        <div>
                            <div className={"status " + (evaluation.passed ? "ok" : "error")} style={{ marginBottom: "8px" }}>
                                Resultat test: {evaluation.passed ? "TRUE" : "FALSE"}
                            </div>
                            <div className="status ok" style={{ marginBottom: "8px" }}>
                                Valeur de retour apply(...): {evaluation.applied === null ? "undefined (aucun match)" : pretty(evaluation.applied)}
                            </div>
                            <label className="label">Trace ad.rules (debug)</label>
                            <div className="trace-list" style={{ maxHeight: "260px" }}>
                                {evaluation.trace.length === 0 && <div className="trace-item">Aucune trace debug.</div>}
                                {evaluation.trace.map(function (line, idx) {
                                    return <div key={idx} className="trace-item">{line}</div>;
                                })}
                            </div>
                        </div>
                        <div>
                            <label className="label">Regle ad.rules generee (JSON)</label>
                            <pre className="example-code" style={{ maxHeight: "320px", overflow: "auto" }}>{pretty(evaluation.logic)}</pre>
                        </div>
                    </div>
                )}
            </section>
        </>
    );
}

function deepClone(value) {
    try {
        return JSON.parse(JSON.stringify(value));
    } catch (e) {
        return value;
    }
}

function listToText(list) {
    return (Array.isArray(list) ? list : []).map(String).join("\n");
}

function textToList(text) {
    if (!text) return [];
    return text
        .split(/\r?\n|,/)
        .map(function (s) { return s.trim(); })
        .filter(Boolean);
}

function parseUrlForHost(value) {
    if (!value) return "";
    var raw = String(value).trim();
    if (!raw) return "";
    try {
        var parsed = new URL(raw.indexOf("://") > -1 ? raw : "https://" + raw);
        return parsed.hostname || "";
    } catch (e) {
        return "";
    }
}

function parseEditorModel(config) {
    var source = config || {};
    var defaults = source.defaults || {};
    var profilesObj = source.profiles || {};
    var profiles = Object.keys(profilesObj).map(function (key) {
        var profile = profilesObj[key] || {};
        var containers = Array.isArray(profile.containers) ? profile.containers : [];
        return {
            key: key,
            internalDomainsText: listToText(profile.internalDomains || []),
            _rawProfile: deepClone(profile),
            containers: containers.map(function (c) {
                return {
                    provider: c && c.provider ? String(c.provider) : "TC",
                    id: c && c.id ? String(c.id) : "",
                    cb: !!(c && c.cb)
                };
            })
        };
    });

    var rules = source.rules || {};
    var domainRules = Array.isArray(rules.domain) ? rules.domain : [];
    var editableRules = [];
    var untouchedDomainRules = [];

    for (var i = 0; i < domainRules.length; i++) {
        var row = domainRules[i];
        var parsed = null;

        if (row && Array.isArray(row["=="]) && row["=="].length >= 3) {
            var eq = row["=="];
            if (eq[2] && typeof eq[2] === "object" && eq[2].profile) {
                parsed = {
                    id: "rule-" + i,
                    target: eq[0] === "href" ? "href" : "host",
                    mode: "equals",
                    value: String(eq[1] || ""),
                    profile: String(eq[2].profile),
                    meta: deepClone(eq[2])
                };
            }
        }

        if (!parsed && row && Array.isArray(row.match) && row.match.length >= 3) {
            var mt = row.match;
            if (mt[2] && typeof mt[2] === "object" && mt[2].profile) {
                parsed = {
                    id: "rule-" + i,
                    target: mt[0] === "href" ? "href" : "host",
                    mode: "regex",
                    value: String(mt[1] || ""),
                    profile: String(mt[2].profile),
                    meta: deepClone(mt[2])
                };
            }
        }

        if (parsed) editableRules.push(parsed);
        else untouchedDomainRules.push(row);
    }

    var otherRuleBuckets = {};
    Object.keys(rules).forEach(function (bucket) {
        if (bucket !== "domain") otherRuleBuckets[bucket] = deepClone(rules[bucket]);
    });

    return {
        defaults: {
            internalDomainsText: listToText(defaults.internalDomains || []),
            paramsToExtractText: listToText(defaults.paramsToExtract || []),
            downloadExtensionsText: listToText(defaults.downloadExtensions || []),
            tcEventMapText: pretty(defaults.tcEventMap || {}),
            trackedClassesText: pretty(defaults.trackedClasses || [])
        },
        profiles: profiles,
        rules: editableRules,
        untouchedDomainRules: untouchedDomainRules,
        otherRuleBuckets: otherRuleBuckets
    };
}

function buildConfigFromEditor(editor, baseConfig) {
    var base = deepClone(baseConfig || {});
    var defaultsBase = base.defaults || {};
    var profiles = {};

    for (var i = 0; i < editor.profiles.length; i++) {
        var p = editor.profiles[i];
        var key = String(p.key || "").trim();
        if (!key) continue;

        var containers = [];
        for (var j = 0; j < p.containers.length; j++) {
            var c = p.containers[j] || {};
            if (!String(c.id || "").trim()) continue;
            containers.push({
                provider: String(c.provider || "TC").trim() || "TC",
                id: String(c.id || "").trim(),
                cb: !!c.cb
            });
        }

        var rawProfile = deepClone(p._rawProfile || {});
        profiles[key] = Object.assign({}, rawProfile, {
            internalDomains: textToList(p.internalDomainsText),
            containers: containers
        });
    }

    var defaults = Object.assign({}, defaultsBase);
    defaults.internalDomains = textToList(editor.defaults.internalDomainsText);
    defaults.paramsToExtract = textToList(editor.defaults.paramsToExtractText);
    defaults.downloadExtensions = textToList(editor.defaults.downloadExtensionsText);

    try {
        defaults.tcEventMap = JSON.parse(editor.defaults.tcEventMapText || "{}");
    } catch (e) {
        throw new Error("tcEventMap JSON invalide: " + e.message);
    }

    try {
        defaults.trackedClasses = JSON.parse(editor.defaults.trackedClassesText || "[]");
    } catch (e) {
        throw new Error("trackedClasses JSON invalide: " + e.message);
    }

    var generatedDomainRules = [];
    for (var k = 0; k < editor.rules.length; k++) {
        var r = editor.rules[k];
        var profile = String(r.profile || "").trim();
        var value = String(r.value || "").trim();
        if (!profile || !value) continue;

        var ruleMeta = deepClone(r.meta || {});
        ruleMeta.profile = profile;

        if (r.mode === "equals") {
            generatedDomainRules.push({
                "==": [r.target === "href" ? "href" : "host", value, ruleMeta]
            });
        } else {
            generatedDomainRules.push({
                match: [r.target === "href" ? "href" : "host", value, ruleMeta]
            });
        }
    }

    var rules = {};
    Object.keys(editor.otherRuleBuckets || {}).forEach(function (bucket) {
        rules[bucket] = deepClone(editor.otherRuleBuckets[bucket]);
    });
    rules.domain = generatedDomainRules.concat(editor.untouchedDomainRules || []);

    return Object.assign({}, base, {
        defaults: defaults,
        profiles: profiles,
        rules: rules
    });
}

function UdlConfigDebug() {
    const udlAvail = !!(window.udl && window.udl.dryRun);

    function publishCurrentConfig(config, source) {
        try {
            window.__udlConfigDebugCurrent = deepClone(config || {});
            window.__udlConfigDebugMeta = {
                updatedAt: new Date().toISOString(),
                source: source || "udl-config-debug"
            };
            window.dispatchEvent(new CustomEvent("udl-config-debug:update", {
                detail: {
                    source: source || "udl-config-debug",
                    updatedAt: window.__udlConfigDebugMeta.updatedAt
                }
            }));
        } catch (e) { }
    }

    const [liveConfig, setLiveConfig] = useState(function () { return deepClone(window.udlConfig || {}); });
    const [editorModel, setEditorModel] = useState(function () { return parseEditorModel(window.udlConfig || {}); });
    const [activeProfile, setActiveProfile] = useState(function () {
        var keys = Object.keys((window.udlConfig && window.udlConfig.profiles) || {});
        return keys[0] || "";
    });
    const [activeRuleId, setActiveRuleId] = useState("");

    const [configText, setConfigText] = useState(function () {
        return pretty(window.udlConfig || {});
    });

    const [status, setStatus] = useState("");
    const [statusType, setStatusType] = useState("ok");
    const [configError, setConfigError] = useState("");
    const [snapshotLabel, setSnapshotLabel] = useState("");
    const [snapshotSource, setSnapshotSource] = useState("udl-config-debug");
    const [snapshotAuthor, setSnapshotAuthor] = useState("backoffice");
    const [snapshotBusy, setSnapshotBusy] = useState(false);

    const [hostInput, setHostInput] = useState(window.location.hostname || "");
    const [urlInput, setUrlInput] = useState(window.location.href || "");
    const [results, setResults] = useState([]);

    function syncFromWindowConfig(statusMessage) {
        var cfg = deepClone(window.udlConfig || {});
        setLiveConfig(cfg);
        setEditorModel(parseEditorModel(cfg));
        setConfigText(pretty(cfg));
        var keys = Object.keys(cfg.profiles || {});
        setActiveProfile(keys[0] || "");
        publishCurrentConfig(cfg, "window.udlConfig");
        if (statusMessage) {
            setStatusType("ok");
            setStatus(statusMessage);
        }
    }

    const profileNames = useMemo(function () {
        return editorModel.profiles.map(function (p) { return p.key; }).filter(Boolean);
    }, [editorModel.profiles]);

    useEffect(function () {
        if (!activeProfile && profileNames.length) setActiveProfile(profileNames[0]);
        if (activeProfile && profileNames.indexOf(activeProfile) === -1) {
            setActiveProfile(profileNames[0] || "");
        }
    }, [activeProfile, profileNames]);

    useEffect(function () {
        var ruleIds = editorModel.rules.map(function (r) { return r.id; });
        if (!activeRuleId && ruleIds.length) {
            setActiveRuleId(ruleIds[0]);
            return;
        }
        if (activeRuleId && ruleIds.indexOf(activeRuleId) === -1) {
            setActiveRuleId(ruleIds[0] || "");
        }
    }, [activeRuleId, editorModel.rules]);

    useEffect(function () {
        var cancelled = false;

        function handleReady() {
            if (cancelled) return;
            syncFromWindowConfig("Configuration chargee depuis udl_config.js.");
            setConfigError("");
        }

        if (window.udlConfig) {
            handleReady();
            return function () {
                cancelled = true;
            };
        }

        if (window.udl && typeof window.udl.ensureDependencies === "function") {
            window.udl.ensureDependencies().then(function () {
                handleReady();
            }).catch(function (e) {
                if (cancelled) return;
                setStatusType("error");
                setStatus("Impossible de charger udl_config.js automatiquement.");
                setConfigError(e && e.message ? e.message : "Chargement des dependances impossible.");
            });
        }

        return function () {
            cancelled = true;
        };
    }, []);

    function withEditor(mutator) {
        setEditorModel(function (prev) {
            var next = deepClone(prev);
            mutator(next);
            return next;
        });
    }

    function applyFormToLiveConfig() {
        try {
            var nextConfig = buildConfigFromEditor(editorModel, liveConfig);
            setLiveConfig(nextConfig);
            setConfigText(pretty(nextConfig));
            publishCurrentConfig(nextConfig, "form");
            setConfigError("");
            setStatusType("ok");
            setStatus("Configuration appliquee depuis l'editeur structure.");
        } catch (e) {
            setConfigError(e.message);
            setStatusType("error");
            setStatus("Impossible d'appliquer le formulaire.");
        }
    }

    function applyJsonToLiveConfig() {
        try {
            var parsed = JSON.parse(configText);
            setLiveConfig(parsed);
            setEditorModel(parseEditorModel(parsed));
            publishCurrentConfig(parsed, "json");
            var keys = Object.keys(parsed.profiles || {});
            if (keys.length) setActiveProfile(keys[0]);
            setConfigError("");
            setStatusType("ok");
            setStatus("Configuration JSON appliquee.");
        } catch (e) {
            setConfigError("JSON invalide: " + e.message);
            setStatusType("error");
            setStatus("Correction requise dans le JSON.");
        }
    }

    function createConfigSnapshotFromDebug() {
        var parsed;
        try {
            parsed = JSON.parse(configText);
        } catch (e) {
            setConfigError("JSON invalide: " + e.message);
            setStatusType("error");
            setStatus("Correction requise dans le JSON avant versioning.");
            return;
        }

        setSnapshotBusy(true);
        fetch("/api/udl-config/snapshot", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
                config: parsed,
                label: snapshotLabel,
                source: snapshotSource,
                author: snapshotAuthor
            })
        }).then(function (res) {
            return parseApiResponseSafe(res, "/api/udl-config/snapshot");
        }).then(function (payload) {
            var version = payload && payload.version ? payload.version : null;
            setStatusType("ok");
            setStatus("Version udl_config enregistree: " + (version ? (version.display || version.id) : "ok"));
        }).catch(function (e) {
            setStatusType("error");
            setStatus("Snapshot impossible: " + e.message);
        }).finally(function () {
            setSnapshotBusy(false);
        });
    }

    function resetFromWindowConfig() {
        syncFromWindowConfig();
        setResults([]);
        setConfigError("");
        setStatusType("ok");
        setStatus("Etat reinitialise depuis window.udlConfig.");
    }

    function addProfile() {
        var nextName = "newProfile";
        var i = 1;
        while (profileNames.indexOf(nextName) !== -1) {
            i += 1;
            nextName = "newProfile" + i;
        }
        withEditor(function (next) {
            next.profiles.push({ key: nextName, internalDomainsText: "", containers: [{ provider: "TC", id: "", cb: true }] });
        });
        setActiveProfile(nextName);
    }

    function removeProfile(name) {
        withEditor(function (next) {
            next.profiles = next.profiles.filter(function (p) { return p.key !== name; });
            next.rules = next.rules.map(function (r) {
                if (r.profile === name) return Object.assign({}, r, { profile: "" });
                return r;
            });
        });
    }

    function addContainer(profileName) {
        withEditor(function (next) {
            var p = next.profiles.find(function (item) { return item.key === profileName; });
            if (!p) return;
            p.containers.push({ provider: "TC", id: "", cb: true });
        });
    }

    function removeContainer(profileName, index) {
        withEditor(function (next) {
            var p = next.profiles.find(function (item) { return item.key === profileName; });
            if (!p) return;
            p.containers = p.containers.filter(function (_, idx) { return idx !== index; });
        });
    }

    function addRule() {
        withEditor(function (next) {
            var newId = "rule-" + Date.now() + "-" + Math.floor(Math.random() * 1000);
            next.rules.push({
                id: newId,
                target: "host",
                mode: "equals",
                value: "",
                profile: profileNames[0] || "",
                meta: { profile: profileNames[0] || "" }
            });
            setActiveRuleId(newId);
        });
    }

    function removeRule(ruleId) {
        withEditor(function (next) {
            next.rules = next.rules.filter(function (r) { return r.id !== ruleId; });
        });
    }

    function updateProfileField(profileName, field, value) {
        withEditor(function (next) {
            var p = next.profiles.find(function (item) { return item.key === profileName; });
            if (!p) return;
            p[field] = value;
        });
    }

    function updateContainer(profileName, index, field, value) {
        withEditor(function (next) {
            var p = next.profiles.find(function (item) { return item.key === profileName; });
            if (!p || !p.containers[index]) return;
            p.containers[index][field] = value;
        });
    }

    function updateRule(ruleId, field, value) {
        withEditor(function (next) {
            var r = next.rules.find(function (item) { return item.id === ruleId; });
            if (!r) return;
            r[field] = value;
        });
    }

    function updateDefaults(field, value) {
        withEditor(function (next) {
            next.defaults[field] = value;
        });
    }

    function runSingleSimulation(hostValue, hrefValue) {
        if (!udlAvail) return;
        var cleanHost = String(hostValue || "").trim() || parseUrlForHost(hrefValue);
        var cleanHref = String(hrefValue || "").trim() || (cleanHost ? "https://" + cleanHost + "/" : "");
        var safePath = "/";
        if (cleanHref) {
            try {
                safePath = new URL(cleanHref.indexOf("://") > -1 ? cleanHref : ("https://" + cleanHref)).pathname || "/";
            } catch (e) {
                safePath = "/";
            }
        }
        var runInput = {
            host: cleanHost,
            href: cleanHref,
            path: safePath
        };
        var output = window.udl.dryRun(runInput, liveConfig);
        setResults([{ host: runInput.host, href: runInput.href, profile: output.profile, config: output.config, logs: output.logs }]);
    }

    function runSimulationMatrix() {
        if (!udlAvail) return;
        var candidates = [];
        for (var i = 0; i < editorModel.rules.length; i++) {
            var r = editorModel.rules[i];
            if (r.mode !== "equals") continue;
            if (r.target === "host") {
                candidates.push({ host: r.value, href: "https://" + r.value + "/" });
            } else {
                candidates.push({ host: parseUrlForHost(r.value), href: r.value });
            }
        }

        var seen = {};
        var runs = [];
        for (var j = 0; j < candidates.length; j++) {
            var c = candidates[j];
            if (!c.host) continue;
            var key = c.host + "|" + c.href;
            if (seen[key]) continue;
            seen[key] = true;
            var out = window.udl.dryRun({ host: c.host, href: c.href, path: "/" }, liveConfig);
            runs.push({ host: c.host, href: c.href, profile: out.profile, config: out.config, logs: out.logs });
        }
        setResults(runs);
    }

    var selectedProfile = editorModel.profiles.find(function (p) { return p.key === activeProfile; });
    var selectedRule = editorModel.rules.find(function (r) { return r.id === activeRuleId; });

    function getProfileSource(profile) {
        if (!profile) return "{}";
        var profileSource = {};
        profileSource[profile.key || "profile"] = {
            internalDomains: textToList(profile.internalDomainsText),
            containers: (profile.containers || []).map(function (c) {
                return {
                    provider: c.provider || "TC",
                    id: c.id || "",
                    cb: !!c.cb
                };
            })
        };
        return pretty(profileSource);
    }

    function getRuleSource(rule) {
        if (!rule) return "{}";
        if (rule.mode === "regex") {
            return pretty({
                match: [rule.target === "href" ? "href" : "host", rule.value || "", { profile: rule.profile || "" }]
            });
        }
        return pretty({
            "==": [rule.target === "href" ? "href" : "host", rule.value || "", { profile: rule.profile || "" }]
        });
    }

    return (
        <>
            <div className="content-header">
                <h1 className="title">UDL Config Debug</h1>
                <p className="subtitle">
                    Ecran d'edition de configuration avec regles de selection de profil par host/url en egalite ou regex.
                </p>
            </div>

            {!udlAvail && (
                <div className="status error">udl.js non charge ou version sans dryRun. Verifiez index.html.</div>
            )}

            <section className="panel runtime-step-panel" style={{ marginBottom: "16px" }}>
                <div className="panel-kicker">Etape 1</div>
                <h3>Gestion des profils</h3>
                <p className="subtitle compact">Liste des profils a gauche, edition detaillee a droite. Les operations de profils sont isolees de celles des regles.</p>
                <div className="udl-manager-grid">
                    <div>
                        <div className="label" style={{ marginBottom: "8px" }}>Liste des profils</div>
                        <div className="udl-list-panel">
                            {editorModel.profiles.length === 0 && <div className="trace-item">Aucun profil. Creez-en un.</div>}
                            {editorModel.profiles.map(function (p) {
                                var containerCount = (p.containers || []).length;
                                return (
                                    <button
                                        type="button"
                                        key={p.key || "profile-empty"}
                                        className={"udl-list-item" + (activeProfile === p.key ? " active" : "")}
                                        onClick={function () { setActiveProfile(p.key); }}
                                    >
                                        <strong>{p.key || "(sans nom)"}</strong>
                                        <span>{containerCount} container(s)</span>
                                    </button>
                                );
                            })}
                        </div>
                        <div className="actions" style={{ marginTop: "10px" }}>
                            <button className="btn secondary" onClick={addProfile}>+ Profil</button>
                        </div>
                    </div>

                    <div>
                        {!selectedProfile && <div className="trace-item">Selectionnez un profil pour editer ses champs.</div>}
                        {selectedProfile && (
                            <div className="udl-stack">
                                <div className="field">
                                    <label className="label">Nom du profil</label>
                                    <input
                                        value={selectedProfile.key}
                                        onChange={function (e) {
                                            var oldName = selectedProfile.key;
                                            var newName = e.target.value;
                                            updateProfileField(oldName, "key", newName);
                                            if (activeProfile === oldName) setActiveProfile(newName);
                                            withEditor(function (next) {
                                                next.rules = next.rules.map(function (r) {
                                                    if (r.profile === oldName) return Object.assign({}, r, { profile: newName });
                                                    return r;
                                                });
                                            });
                                        }}
                                    />
                                </div>

                                <div className="field">
                                    <label className="label">internalDomains (1 ligne = 1 entree)</label>
                                    <textarea
                                        style={{ minHeight: "110px" }}
                                        value={selectedProfile.internalDomainsText}
                                        onChange={function (e) { updateProfileField(selectedProfile.key, "internalDomainsText", e.target.value); }}
                                    />
                                </div>

                                <div className="field">
                                    <label className="label">Containers</label>
                                    <div className="udl-stack">
                                        {selectedProfile.containers.map(function (c, idx) {
                                            return (
                                                <div key={idx} className="udl-container-row">
                                                    <select value={c.provider} onChange={function (e) { updateContainer(selectedProfile.key, idx, "provider", e.target.value); }}>
                                                        <option value="TC">TC</option>
                                                        <option value="GTM">GTM</option>
                                                    </select>
                                                    <input
                                                        value={c.id}
                                                        placeholder="https://cdn..."
                                                        onChange={function (e) { updateContainer(selectedProfile.key, idx, "id", e.target.value); }}
                                                    />
                                                    <label className="label" style={{ display: "flex", alignItems: "center", gap: "6px" }}>
                                                        <input
                                                            style={{ width: "auto" }}
                                                            type="checkbox"
                                                            checked={!!c.cb}
                                                            onChange={function (e) { updateContainer(selectedProfile.key, idx, "cb", e.target.checked); }}
                                                        />
                                                        cb
                                                    </label>
                                                    <button className="btn secondary" onClick={function () { removeContainer(selectedProfile.key, idx); }}>Suppr</button>
                                                </div>
                                            );
                                        })}
                                    </div>
                                    <div className="actions">
                                        <button className="btn secondary" onClick={function () { addContainer(selectedProfile.key); }}>+ Container</button>
                                        <button className="btn secondary" onClick={function () { removeProfile(selectedProfile.key); }}>Supprimer profil</button>
                                    </div>
                                </div>

                                <div className="field">
                                    <label className="label">Code source du profil</label>
                                    <pre className="example-code" style={{ maxHeight: "240px", overflow: "auto" }}>{getProfileSource(selectedProfile)}</pre>
                                </div>
                            </div>
                        )}
                    </div>
                </div>

                <div className="actions">
                    <button className="btn" onClick={applyFormToLiveConfig}>Appliquer le formulaire</button>
                    <button className="btn secondary" onClick={resetFromWindowConfig}>Reinitialiser</button>
                </div>
            </section>

            <section className="panel runtime-step-panel domain-panel" style={{ marginBottom: "16px" }}>
                <div className="panel-kicker">Etape 2</div>
                <h3>Gestion des regles de selection de profil</h3>
                <p className="subtitle compact">Liste des regles a gauche, formulaire d'edition a droite. Les regex sont editees dans un champ multi-lignes pour une lecture plus claire.</p>
                <div className="udl-manager-grid">
                    <div>
                        <div className="label" style={{ marginBottom: "8px" }}>Liste des regles</div>
                        <div className="udl-list-panel">
                            {editorModel.rules.length === 0 && <div className="trace-item">Aucune regle. Ajoutez une regle de selection.</div>}
                            {editorModel.rules.map(function (rule, idx) {
                                return (
                                    <button
                                        type="button"
                                        key={rule.id}
                                        className={"udl-list-item" + (activeRuleId === rule.id ? " active" : "")}
                                        onClick={function () { setActiveRuleId(rule.id); }}
                                    >
                                        <strong>Regle {idx + 1} - {rule.mode === "regex" ? "regex" : "egalite"}</strong>
                                        <span>{rule.target}: {rule.value || "(valeur vide)"}</span>
                                        <span>Profil: {rule.profile || "(non renseigne)"}</span>
                                    </button>
                                );
                            })}
                        </div>
                        <div className="actions" style={{ marginTop: "10px" }}>
                            <button className="btn secondary" onClick={addRule}>+ Regle</button>
                        </div>
                    </div>

                    <div>
                        {!selectedRule && <div className="trace-item">Selectionnez une regle pour l'editer.</div>}
                        {selectedRule && (
                            <div className="udl-stack">
                                <div className="udl-rule-form-grid">
                                    <div className="field">
                                        <label className="label">Cible</label>
                                        <select value={selectedRule.target} onChange={function (e) { updateRule(selectedRule.id, "target", e.target.value); }}>
                                            <option value="host">host (domaine)</option>
                                            <option value="href">url complete (href)</option>
                                        </select>
                                    </div>

                                    <div className="field">
                                        <label className="label">Mode</label>
                                        <select value={selectedRule.mode} onChange={function (e) { updateRule(selectedRule.id, "mode", e.target.value); }}>
                                            <option value="equals">egalite</option>
                                            <option value="regex">regex</option>
                                        </select>
                                    </div>

                                    <div className="field">
                                        <label className="label">Profil cible</label>
                                        <select value={selectedRule.profile} onChange={function (e) { updateRule(selectedRule.id, "profile", e.target.value); }}>
                                            <option value="">profil...</option>
                                            {profileNames.map(function (pName) {
                                                return <option key={pName} value={pName}>{pName}</option>;
                                            })}
                                        </select>
                                    </div>
                                </div>

                                <div className="field">
                                    <label className="label">Valeur de la regle</label>
                                    {selectedRule.mode === "regex" ? (
                                        <textarea
                                            style={{ minHeight: "84px", fontFamily: "Consolas,Monaco,monospace" }}
                                            value={selectedRule.value}
                                            placeholder="ex: (^particulier\\.edf\\.fr$)|(^souscrire-recette\\.edf\\.fr$)"
                                            onChange={function (e) { updateRule(selectedRule.id, "value", e.target.value); }}
                                        />
                                    ) : (
                                        <input
                                            value={selectedRule.value}
                                            placeholder="ex: particulier.edf.fr"
                                            onChange={function (e) { updateRule(selectedRule.id, "value", e.target.value); }}
                                        />
                                    )}
                                </div>

                                <div className="field">
                                    <label className="label">Expression ad.rules generee</label>
                                    <pre className="example-code" style={{ maxHeight: "220px", overflow: "auto" }}>{getRuleSource(selectedRule)}</pre>
                                </div>

                                <div className="actions">
                                    <button className="btn secondary" onClick={function () { removeRule(selectedRule.id); }}>Supprimer regle</button>
                                </div>
                            </div>
                        )}
                    </div>
                </div>
                <div className="actions">
                    <button className="btn" onClick={applyFormToLiveConfig}>Appliquer le formulaire</button>
                </div>
            </section>

            <div className="runtime-lab-grid" style={{ marginBottom: "16px" }}>
                <section className="panel runtime-step-panel domain-panel">
                    <div className="panel-kicker">Annexe / Versioning</div>
                    <h3>Versionner udl_config depuis l'annexe</h3>
                    <div className="field" style={{ marginTop: "8px" }}>
                        <label className="label">Configuration active (modifiable)</label>
                        <CodeEditor
                            language="json"
                            value={configText}
                            onChange={setConfigText}
                            height={360}
                        />
                    </div>
                    <div className="actions">
                        <button className="btn" onClick={applyJsonToLiveConfig}>Appliquer JSON</button>
                    </div>

                    <div className="udl-manager-grid" style={{ marginTop: "10px" }}>
                        <div className="field">
                            <label className="label">Label de version</label>
                            <input value={snapshotLabel} onChange={function (e) { setSnapshotLabel(e.target.value); }} placeholder="ex: rules regex juillet" />
                        </div>
                        <div className="field">
                            <label className="label">Source</label>
                            <input value={snapshotSource} onChange={function (e) { setSnapshotSource(e.target.value); }} placeholder="ex: udl-config-debug" />
                        </div>
                        <div className="field">
                            <label className="label">Auteur</label>
                            <input value={snapshotAuthor} onChange={function (e) { setSnapshotAuthor(e.target.value); }} placeholder="ex: jdoe" />
                        </div>
                    </div>
                    <div className="actions">
                        <button className="btn secondary" onClick={createConfigSnapshotFromDebug} disabled={snapshotBusy}>
                            {snapshotBusy ? "Versioning..." : "Enregistrer une version udl_config"}
                        </button>
                    </div>
                    {configError && <div className="status error">{configError}</div>}
                    {status ? <div className={"status " + statusType}>{status}</div> : null}
                </section>

                <section className="panel runtime-step-panel runtime-panel">
                    <div className="panel-kicker">Etape 3</div>
                    <h3>URL a tester</h3>
                    <div className="field" style={{ marginTop: "8px" }}>
                        <label className="label">Host (domaine)</label>
                        <input
                            type="text"
                            value={hostInput}
                            onChange={function (e) { setHostInput(e.target.value); }}
                            placeholder="ex: particulier.edf.fr"
                        />
                    </div>

                    <div className="field">
                        <label className="label">URL complete (href)</label>
                        <input
                            type="text"
                            value={urlInput}
                            onChange={function (e) { setUrlInput(e.target.value); }}
                            placeholder="ex: https://particulier.edf.fr/souscription?x=1"
                        />
                    </div>

                    <div className="actions">
                        <button className="btn" onClick={function () { runSingleSimulation(hostInput, urlInput); }}>Tester</button>
                        <button className="btn secondary" onClick={runSimulationMatrix}>Tester toutes les regles en egalite</button>
                        <button className="btn secondary" onClick={function () { setResults([]); }}>Effacer</button>
                    </div>
                </section>
            </div>

            {results.length > 1 && (
                <section className="panel runtime-step-panel examples-panel">
                    <div className="panel-kicker">Resultats</div>
                    <h3>Synthese multi-cas</h3>
                    <table className="debug-table" style={{ width: "100%" }}>
                        <thead>
                            <tr>
                                <th className="dt-key">Host</th>
                                <th className="dt-val">URL</th>
                                <th className="dt-val">Profil</th>
                                <th className="dt-val">Containers</th>
                            </tr>
                        </thead>
                        <tbody>
                            {results.map(function (r, i) {
                                return (
                                    <tr key={i}>
                                        <td className="dt-key">{r.host}</td>
                                        <td className="dt-val">{r.href}</td>
                                        <td className={"dt-val" + (r.profile ? " dt-highlight" : "")}>{r.profile || "aucun (defaults)"}</td>
                                        <td className="dt-val">
                                            {(r.config.containers || []).map(function (c, j) {
                                                return <div key={j}>{c.provider}: {c.id.split("/").pop()}{c.cb ? " [CB]" : ""}</div>;
                                            })}
                                            {!(r.config.containers || []).length && <span style={{ color: "#aaa" }}>aucun</span>}
                                        </td>
                                    </tr>
                                );
                            })}
                        </tbody>
                    </table>
                </section>
            )}

            {results.map(function (r, idx) {
                return (
                    <div key={idx} style={{ marginTop: "16px" }}>
                        <div className="label" style={{ marginBottom: "6px", fontSize: "1rem", fontWeight: "700" }}>
                            Host: {r.host} | URL: {r.href}
                        </div>
                        <div className="runtime-lab-grid">
                            <section className="panel">
                                <h3>Profil &amp; Containers</h3>
                                <table className="debug-table">
                                    <tbody>
                                        <tr>
                                            <td className="dt-key">Profil selectionne</td>
                                            <td className={"dt-val" + (r.profile ? " dt-highlight" : "")}>{r.profile || "aucun (defaults)"}</td>
                                        </tr>
                                        <tr>
                                            <td className="dt-key">Containers</td>
                                            <td className="dt-val">
                                                {(r.config.containers || []).length === 0 && <span style={{ color: "#aaa" }}>aucun</span>}
                                                {(r.config.containers || []).map(function (c, j) {
                                                    return <div key={j}><strong>{c.provider}</strong> - {c.id}{c.cb ? " [CB]" : ""}</div>;
                                                })}
                                            </td>
                                        </tr>
                                        <tr>
                                            <td className="dt-key">Domaines internes</td>
                                            <td className="dt-val">{(r.config.internalDomains || []).map(String).join(", ") || "-"}</td>
                                        </tr>
                                        <tr>
                                            <td className="dt-key">Params extraits</td>
                                            <td className="dt-val">{(r.config.paramsToExtract || []).join(", ") || "-"}</td>
                                        </tr>
                                    </tbody>
                                </table>
                            </section>

                            <section className="panel">
                                <h3>Journal de resolution</h3>
                                <div className="trace-list" style={{ maxHeight: "260px" }}>
                                    {r.logs.map(function (line, li) {
                                        return (
                                            <div key={li} className={"trace-item" +
                                                (line.startsWith("TMS") ? " trace-tms" :
                                                    line.startsWith("Profil") || line.startsWith("Critere") ? " trace-profile" :
                                                        line.startsWith("Aucune") ? " trace-warn" : "")}
                                            >
                                                {line}
                                            </div>
                                        );
                                    })}
                                </div>
                            </section>
                        </div>
                    </div>
                );
            })}
        </>
    );
}

function UdlRuntimeLab() {
    const DEFAULT_VERSION_OPTION = {
        id: "active",
        serial: 0,
        createdAt: "",
        actor: "workspace",
        label: "active file",
        display: "#0 | active | workspace | active file"
    };

    const domainPresets = [
        { label: "Particulier PROD", host: "particulier.edf.fr", href: "https://particulier.edf.fr/" },
        { label: "Particulier UAT", host: "souscrire-recette.edf.fr", href: "https://souscrire-recette.edf.fr/" },
        { label: "Panel PROD", host: "panel-particuliers.dn.edf.fr", href: "https://panel-particuliers.dn.edf.fr/" },
        { label: "Panel UAT", host: "panel-particuliers-pp.dn.edf.fr", href: "https://panel-particuliers-pp.dn.edf.fr/" },
        { label: "Localhost", host: "localhost", href: "http://localhost:3000/" }
    ];

    const eventTemplates = [
        {
            id: "page_view",
            label: "Chargement page (screen_view)",
            payload: { event: "screen_view", screen_name: "home", page_type: "landing" }
        },
        {
            id: "click_action",
            label: "Action bouton (click_action)",
            payload: { event: "click_action", ev_type: "button", ev_label: "cta_main", ev_zone: "header" }
        },
        {
            id: "click_navigation",
            label: "Navigation lien (click_navigation)",
            payload: { event: "click_navigation", ev_type: "link", ev_label: "menu_offres", ev_zone: "nav" }
        },
        {
            id: "promo_view",
            label: "Autopromo view (promo_view)",
            payload: {
                event: "promo_view",
                promo_id: "PROMO_DEMO",
                promo_name: "Banniere Home",
                promo_creative: "banner_top",
                promo_position: "top"
            }
        }
    ];

    const [udlVersionOptions, setUdlVersionOptions] = useState([DEFAULT_VERSION_OPTION]);
    const [adLibVersionOptions, setAdLibVersionOptions] = useState([DEFAULT_VERSION_OPTION]);
    const [configVersionOptions, setConfigVersionOptions] = useState([DEFAULT_VERSION_OPTION]);
    const [selectedUdlVersionId, setSelectedUdlVersionId] = useState("active");
    const [selectedAdLibVersionId, setSelectedAdLibVersionId] = useState("active");
    const [selectedConfigVersionId, setSelectedConfigVersionId] = useState("active");
    const [activeConfig, setActiveConfig] = useState(function () { return deepClone(window.udlConfig || {}); });
    const [activeConfigText, setActiveConfigText] = useState(function () { return pretty(window.udlConfig || {}); });
    const [contextTab, setContextTab] = useState("form");
    const [depsReady, setDepsReady] = useState(!!window.udlConfig);
    const [runtimeReady, setRuntimeReady] = useState(!!window.udl);
    const [udlStarted, setUdlStarted] = useState(!!window.__udlDataLayerPatched);
    const [hostInput, setHostInput] = useState(window.location.hostname || "localhost");
    const [hrefInput, setHrefInput] = useState(window.location.href || "https://localhost/");
    const [dryRunResult, setDryRunResult] = useState(null);
    const [selectedTemplateId, setSelectedTemplateId] = useState("page_view");
    const [eventPayloadText, setEventPayloadText] = useState(function () {
        return pretty(eventTemplates[0].payload);
    });
    const [status, setStatus] = useState("Pret.");
    const [statusType, setStatusType] = useState("ok");
    const [traces, setTraces] = useState([]);
    const [execLogs, setExecLogs] = useState([]);
    const [configUndoStack, setConfigUndoStack] = useState([]);
    const [configRedoStack, setConfigRedoStack] = useState([]);
    const [isResolving, setIsResolving] = useState(false);
    const [isStarting, setIsStarting] = useState(false);

    const udlLoaded = !!window.udl;

    useEffect(function () {
        var prepared = window.__udlRuntimeLabVersionSelection || null;
        if (!prepared || typeof prepared !== "object") return;
        if (prepared.udlVersionId) setSelectedUdlVersionId(String(prepared.udlVersionId));
        if (prepared.adLibVersionId) setSelectedAdLibVersionId(String(prepared.adLibVersionId));
        if (prepared.configVersionId) setSelectedConfigVersionId(String(prepared.configVersionId));
    }, []);

    useEffect(function () {
        function hook(entry) {
            setTraces(function (prev) {
                var next = prev.length > 250 ? prev.slice(prev.length - 250) : prev.slice();
                next.push(entry);
                return next;
            });
        }

        function udlDebugHook(entry) {
            var args = Array.isArray(entry.args) ? entry.args : [];
            var message = args.length ? String(args[0]) : "[UDL] log";
            var details = args.length > 1 ? args[1] : null;

            setExecLogs(function (prev) {
                var next = prev.length > 400 ? prev.slice(prev.length - 400) : prev.slice();
                next.push({
                    at: entry.time && typeof entry.time.toISOString === "function" ? entry.time.toISOString() : new Date().toISOString(),
                    step: "udl",
                    message: message,
                    details: details,
                    level: entry.level || "info"
                });
                return next;
            });
        }

        window.__installUdlRuntimeWriters();
        if (typeof window.__installUdlDebugWriter === "function") {
            window.__installUdlDebugWriter();
        }
        window.__udlRuntimeHooks.push(hook);
        window.__udlDebugHooks.push(udlDebugHook);

        Promise.all([
            fetch("/api/udl-source/version-options/udl-runtime").then(function (res) { return parseApiResponseSafe(res, "/api/udl-source/version-options/udl-runtime"); }),
            fetch("/api/udl-source/version-options/ad-lib").then(function (res) { return parseApiResponseSafe(res, "/api/udl-source/version-options/ad-lib"); }),
            fetch("/api/udl-config/versions").then(function (res) { return parseApiResponseSafe(res, "/api/udl-config/versions"); })
        ]).then(function (payloads) {
            var udlVersions = ensureVersionList(payloads[0] && payloads[0].versions ? payloads[0].versions : []);
            var adLibVersions = ensureVersionList(payloads[1] && payloads[1].versions ? payloads[1].versions : []);
            var configVersions = ensureVersionList(payloads[2] && payloads[2].versions ? payloads[2].versions : []);

            setUdlVersionOptions(udlVersions);
            setAdLibVersionOptions(adLibVersions);
            setConfigVersionOptions(configVersions);
            setSelectedUdlVersionId("active");
            setSelectedAdLibVersionId("active");
            setSelectedConfigVersionId("active");

            // Charge ad_lib + udl_config actifs des l'ouverture, sans necessiter le runtime UDL.
            return ensureUdlDependencies();
        }).then(function () {
            if (typeof window.__installUdlDebugWriter === "function") {
                window.__installUdlDebugWriter();
            }
            var cfg = deepClone(window.udlConfig || {});
            setActiveConfig(cfg);
            setActiveConfigText(pretty(cfg));
            setConfigUndoStack([]);
            setConfigRedoStack([]);
            setDepsReady(true);
            addExecLog("init", "Version lists chargees + udl_config applique", null, "ok");
        }).catch(function (e) {
            setDepsReady(false);
            addExecLog("init", "Echec initialisation Runtime Lab", { message: e && e.message ? e.message : String(e) }, "error");
        });

        return function () {
            window.__udlRuntimeHooks = window.__udlRuntimeHooks.filter(function (h) { return h !== hook; });
            window.__udlDebugHooks = window.__udlDebugHooks.filter(function (h) { return h !== udlDebugHook; });
        };
    }, []);

    function setOk(message) {
        setStatusType("ok");
        setStatus(message);
        addExecLog("status", message, null, "ok");
    }

    function setError(message) {
        setStatusType("error");
        setStatus(message);
        addExecLog("status", message, null, "error");
    }

    function addExecLog(step, message, details, level) {
        setExecLogs(function (prev) {
            var next = prev.length > 400 ? prev.slice(prev.length - 400) : prev.slice();
            next.push({
                at: new Date().toISOString(),
                step: step || "runtime",
                message: message || "",
                details: details || null,
                level: level || "info"
            });
            return next;
        });
    }

    function ensureVersionList(list) {
        var safeList = Array.isArray(list) ? list.slice() : [];
        var hasActive = safeList.some(function (row) { return row && row.id === "active"; });
        if (!hasActive) {
            safeList.unshift(DEFAULT_VERSION_OPTION);
        }
        return safeList;
    }

    function fetchSourceVersion(sourceId, versionId) {
        var safeVersionId = String(versionId || "active");
        if (safeVersionId === "active") {
            return fetch("/api/udl-source/file/" + encodeURIComponent(sourceId))
                .then(function (res) { return parseApiResponseSafe(res, "/api/udl-source/file/:id"); })
                .then(function (payload) {
                    return {
                        id: "active",
                        content: String(payload.content || ""),
                        meta: payload.file || null
                    };
                });
        }

        return fetch("/api/udl-source/history/" + encodeURIComponent(sourceId) + "/" + encodeURIComponent(safeVersionId))
            .then(function (res) { return parseApiResponseSafe(res, "/api/udl-source/history/:id/:revisionId"); })
            .then(function (payload) {
                return {
                    id: safeVersionId,
                    content: String(payload.content || ""),
                    meta: payload.revision || null
                };
            });
    }

    function fetchConfigVersion(versionId) {
        var safeVersionId = String(versionId || "active");
        return fetch("/api/udl-config/version/" + encodeURIComponent(safeVersionId))
            .then(function (res) { return parseApiResponseSafe(res, "/api/udl-config/version/:id"); })
            .then(function (payload) {
                return {
                    id: safeVersionId,
                    content: String(payload.content || ""),
                    meta: payload.version || null
                };
            });
    }

    function getRuntimeSessionCb() {
        var storageKey = "udl_session_cb";

        if (window.__udlSessionId) {
            return String(window.__udlSessionId);
        }

        if (window.__backofficeUdlSessionCb) {
            return String(window.__backofficeUdlSessionCb);
        }

        var generated = Date.now().toString();
        try {
            var fromStorage = sessionStorage.getItem(storageKey);
            var value = fromStorage || generated;
            if (!fromStorage) {
                sessionStorage.setItem(storageKey, value);
            }
            window.__udlSessionId = String(value);
            window.__backofficeUdlSessionCb = String(value);
            return String(value);
        } catch (e) {
            window.__backofficeUdlSessionCb = generated;
            return generated;
        }
    }

    function loadScriptOnce(scriptPath, options) {
        var opts = options || {};
        var globalName = opts.globalName;
        if (globalName && typeof window[globalName] !== "undefined" && window[globalName] !== null) {
            addExecLog("loadScriptOnce", "Script deja present via global: " + globalName, { scriptPath: scriptPath }, "info");
            return Promise.resolve();
        }

        var src = scriptPath;
        if (opts.useCb) {
            src += (src.indexOf("?") === -1 ? "?cb=" : "&cb=") + getRuntimeSessionCb();
        }
        addExecLog("loadScriptOnce", "Chargement script", { src: src }, "info");

        return new Promise(function (resolve, reject) {
            var script = document.createElement("script");
            script.src = src;
            script.async = false;
            script.onload = function () {
                addExecLog("loadScriptOnce", "Script charge", { src: src }, "ok");
                resolve();
            };
            script.onerror = function () {
                addExecLog("loadScriptOnce", "Erreur chargement script", { src: src }, "error");
                reject(new Error("Chargement impossible: " + src));
            };
            (document.head || document.documentElement).appendChild(script);
        });
    }

    function loadScriptFromContent(content, label) {
        var scriptSource = String(content || "");
        if (!scriptSource.trim()) {
            return Promise.reject(new Error("Contenu vide pour " + label + "."));
        }

        var withSourceUrl = scriptSource + "\n//# sourceURL=" + label;
        var blob = new Blob([withSourceUrl], { type: "text/javascript" });
        var src = URL.createObjectURL(blob);

        // Note: les URL blob: sont deja uniques par appel a createObjectURL,
        // un suffixe ?cb= les rend introuvables (ERR_FILE_NOT_FOUND) - pas de cache-buster ici.

        addExecLog("loadScriptFromContent", "Injection script", { label: label }, "info");

        return new Promise(function (resolve, reject) {
            var script = document.createElement("script");
            script.src = src;
            script.async = false;
            script.onload = function () {
                URL.revokeObjectURL(src);
                addExecLog("loadScriptFromContent", "Script injecte", { label: label }, "ok");
                resolve();
            };
            script.onerror = function () {
                URL.revokeObjectURL(src);
                reject(new Error("Chargement impossible pour " + label + "."));
            };
            (document.head || document.documentElement).appendChild(script);
        });
    }

    function applyDomainPreset(index) {
        var preset = domainPresets[index];
        if (!preset) return;
        setHostInput(preset.host);
        setHrefInput(preset.href);
        setOk("Preset domaine charge: " + preset.label);
    }

    function getDomainInput() {
        var cleanHost = String(hostInput || "").trim() || parseUrlForHost(hrefInput);
        var cleanHref = String(hrefInput || "").trim() || (cleanHost ? "https://" + cleanHost + "/" : "");
        var safePath = "/";
        if (cleanHref) {
            try {
                safePath = new URL(cleanHref.indexOf("://") > -1 ? cleanHref : ("https://" + cleanHref)).pathname || "/";
            } catch (e) {
                safePath = "/";
            }
        }
        return { host: cleanHost, href: cleanHref, path: safePath };
    }

    function loadUdlFile() {
        addExecLog("loadUdlFile", "Demarrage chargement runtime depuis versions", {
            udlRevisionId: selectedUdlVersionId,
            adLibRevisionId: selectedAdLibVersionId,
            configVersionId: selectedConfigVersionId
        }, "info");

        try {
            delete window.udl;
            delete window.ad;
            delete window.udlConfig;
            delete window.__udlDependenciesReadyPromise;
            addExecLog("loadUdlFile", "Reinitialisation globals runtime/dependances effectuee", null, "info");
        } catch (e) { }

        Promise.all([
            fetchSourceVersion("ad-lib", selectedAdLibVersionId),
            fetchConfigVersion(selectedConfigVersionId),
            fetchSourceVersion("udl-runtime", selectedUdlVersionId)
        ]).then(function (resolved) {
            var adLib = resolved[0];
            var config = resolved[1];
            var udl = resolved[2];

            return loadScriptFromContent(adLib.content, "selected-ad_lib.js")
                .then(function () {
                    return loadScriptFromContent(config.content, "selected-udl_config.js");
                })
                .then(function () {
                    var cfg = deepClone(window.udlConfig || {});
                    setActiveConfig(cfg);
                    setActiveConfigText(pretty(cfg));
                })
                .then(function () {
                    return loadScriptFromContent(udl.content, "selected-udl.js");
                });
        }).then(function () {
            window.__installUdlRuntimeWriters();
            if (typeof window.__installUdlDebugWriter === "function") {
                window.__installUdlDebugWriter();
            }
            setDepsReady(!!window.ad && !!window.udlConfig);
            setRuntimeReady(!!window.udl);
            setUdlStarted(false);
            setOk("Runtime charge avec les versions selectionnees.");
        }).catch(function (e) {
            setRuntimeReady(false);
            setDepsReady(false);
            setUdlStarted(false);
            setError(e && e.message ? e.message : "Echec de chargement des versions selectionnees.");
        });
    }

    function loadConfigOnly() {
        addExecLog("config", "Chargement explicite de la version udl_config selectionnee", { configVersionId: selectedConfigVersionId }, "info");
        fetchConfigVersion(selectedConfigVersionId).then(function (hit) {
            return loadScriptFromContent(hit.content, "selected-udl_config.js").then(function () {
                var cfg = deepClone(window.udlConfig || {});
                setActiveConfig(cfg);
                setActiveConfigText(pretty(cfg));
                setDepsReady(!!window.ad && !!window.udlConfig);
                setOk("udl_config charge depuis la version selectionnee.");
            });
        }).catch(function (e) {
            setDepsReady(false);
            setError(e && e.message ? e.message : "Echec chargement udl_config versionnee.");
        });
    }

    function ensureUdlDependencies() {
        addExecLog("dependencies", "Verification/chargement dependances UDL versionnees", {
            adLibRevisionId: selectedAdLibVersionId,
            configVersionId: selectedConfigVersionId
        }, "info");

        return Promise.all([
            fetchSourceVersion("ad-lib", selectedAdLibVersionId),
            fetchConfigVersion(selectedConfigVersionId)
        ]).then(function (resolved) {
            var adLib = resolved[0];
            var config = resolved[1];

            try {
                delete window.ad;
                delete window.udlConfig;
            } catch (e) { }

            return loadScriptFromContent(adLib.content, "selected-ad_lib.js")
                .then(function () {
                    return loadScriptFromContent(config.content, "selected-udl_config.js");
                })
                .then(function () {
                    if (!window.ad || !window.ad.rules) {
                        throw new Error("ad.rules indisponible apres chargement dependances.");
                    }
                    setDepsReady(true);
                    var cfg = deepClone(window.udlConfig || {});
                    setActiveConfig(cfg);
                    setActiveConfigText(pretty(cfg));
                    addExecLog("dependencies", "Dependances UDL OK (ad.rules + udlConfig)", null, "ok");
                });
        });
    }

    function resolveConfigForDomain(input, configSource) {
        var source = configSource || window.udlConfig || {};

        if (Array.isArray(source)) {
            for (var i = 0; i < source.length; i++) {
                if (source[i].regex && source[i].regex.test(input.host)) {
                    return {
                        profile: "legacy",
                        selected: null,
                        config: Object.assign({}, source[i].settings || {}),
                        logs: ["Mode legacy regex: match"]
                    };
                }
            }
            return { profile: null, selected: null, config: {}, logs: ["Aucune regle legacy ne correspond."] };
        }

        var defaults = source.defaults || {};
        var selected = null;
        if (window.ad && ad.rules && source.rules) {
            var previous = ad.rules.returnStringUndefined;
            if (typeof ad.rules.setUndefinedMode === "function") ad.rules.setUndefinedMode(false);
            selected = ad.rules.evaluate(source.rules, {
                host: input.host,
                path: input.path,
                href: input.href
            });
            if (typeof ad.rules.setUndefinedMode === "function") ad.rules.setUndefinedMode(previous);
        }

        var profiles = source.profiles || {};
        var profileName = null;
        var profileConfig = {};
        var overrides = {};
        if (selected && typeof selected === "object") {
            if (selected.profile && profiles[selected.profile]) {
                profileName = selected.profile;
                profileConfig = profiles[selected.profile];
            }
            if (selected.settings && typeof selected.settings === "object") {
                overrides = selected.settings;
            }
        }

        return {
            profile: profileName,
            selected: selected,
            config: Object.assign({}, defaults, profileConfig, overrides),
            logs: []
        };
    }

    function resolveDomainWithDependencies(options) {
        var opts = options || {};
        var silentStatus = !!opts.silentStatus;

        var workingConfig = parseWorkingConfigFromEditor();
        if (!workingConfig) return Promise.resolve(null);

        var input = getDomainInput();
        if (!input.host) {
            setError("Host simule requis pour resoudre le profil.");
            return Promise.resolve(null);
        }

        setIsResolving(true);
        addExecLog("dryRun", "Resolution domaine demandee", input, "info");

        return ensureUdlDependencies().then(function () {
            var output = (window.udl && typeof window.udl.dryRun === "function")
                ? window.udl.dryRun(input, workingConfig)
                : resolveConfigForDomain(input, workingConfig);

            var safeOutput = deepClone(output || {});
            setDryRunResult(safeOutput);
            addExecLog("dryRun", "Resolution domaine terminee", {
                host: input.host,
                profile: safeOutput.profile || "defaults",
                containers: safeOutput.config && safeOutput.config.containers ? safeOutput.config.containers : []
            }, "ok");

            if (!silentStatus) {
                var noRulesEngine = (safeOutput.logs || []).some(function (line) {
                    return /ad\.rules non disponible/i.test(String(line || ""));
                });

                if (noRulesEngine) {
                    setError("Resolution en mode defaults: ad.rules indisponible.");
                } else {
                    setOk("Resolution terminee pour host: " + input.host + (safeOutput.profile ? " -> " + safeOutput.profile : " -> defaults"));
                }
            }

            return safeOutput;
        }).catch(function (e) {
            setError(e && e.message ? e.message : "Impossible de preparer les dependances pour dryRun.");
            return null;
        }).finally(function () {
            setIsResolving(false);
        });
    }

    function runDomainResolution() {
        if (isResolving || isStarting) return null;
        resolveDomainWithDependencies({ silentStatus: false });
        return null;
    }

    function startForResolvedDomain() {
        if (isStarting || isResolving) return;

        if (!window.udl || typeof window.udl.start !== "function") {
            setError("window.udl.start est indisponible.");
            return;
        }

        var domainInput = getDomainInput();
        setIsStarting(true);
        addExecLog("start", "Initialisation UDL pour domaine simule", domainInput, "info");

        resolveDomainWithDependencies({ silentStatus: true }).then(function (output) {
            if (!output) {
                setUdlStarted(false);
                setIsStarting(false);
                return;
            }

            if (!output || !output.config) {
                setError("Aucune configuration resolue pour ce domaine.");
                setIsStarting(false);
                return;
            }

            // Force la configuration resolue pour simuler un domaine different sans changer l'URL de la page.
            var forcedRuntimeConfig = { defaults: output.config, profiles: {}, rules: {} };
            addExecLog("start", "Configuration forcee calculee", {
                profile: output.profile || "defaults",
                containers: output.config && output.config.containers ? output.config.containers : []
            }, "info");

            try {
                var startResult = window.udl.start(forcedRuntimeConfig);
                if (startResult && typeof startResult.then === "function") {
                    startResult.then(function () {
                        window.__installUdlRuntimeWriters();
                        setUdlStarted(true);
                        setOk("UDL initialise pour domaine simule (async). Profil: " + (output.profile || "defaults"));
                    }).catch(function (e) {
                        setUdlStarted(false);
                        setError(e && e.message ? e.message : "Erreur start async.");
                    }).finally(function () {
                        setIsStarting(false);
                    });
                } else {
                    window.__installUdlRuntimeWriters();
                    setUdlStarted(true);
                    setOk("UDL initialise pour domaine simule. Profil: " + (output.profile || "defaults"));
                    setIsStarting(false);
                }
            } catch (e) {
                setUdlStarted(false);
                setError("Erreur start: " + e.message);
                setIsStarting(false);
            }
        });
    }

    function resetConfigFromWindow() {
        var cfg = deepClone(window.udlConfig || {});
        setActiveConfig(cfg);
        setActiveConfigText(pretty(cfg));
        setConfigUndoStack([]);
        setConfigRedoStack([]);
        setDryRunResult(null);
        setDepsReady(!!window.udlConfig);
        setOk("udl_config.js recharge depuis le fichier distant.");
    }

    function parseWorkingConfigFromEditor() {
        try {
            var parsed = activeConfigText.trim() ? JSON.parse(activeConfigText) : {};
            setActiveConfig(parsed);
            return parsed;
        } catch (e) {
            setError("Configuration invalide dans l'editeur: " + e.message);
            return null;
        }
    }

    function onConfigEditorChange(nextText) {
        if (nextText === activeConfigText) return;
        setConfigUndoStack(function (prev) {
            var next = prev.concat([activeConfigText]);
            return next.length > 100 ? next.slice(next.length - 100) : next;
        });
        setConfigRedoStack([]);
        setActiveConfigText(nextText);
    }

    function undoConfigEdit() {
        if (!configUndoStack.length) return;

        var previousText = configUndoStack[configUndoStack.length - 1];
        setConfigUndoStack(configUndoStack.slice(0, -1));
        setConfigRedoStack(function (prev) {
            var next = prev.concat([activeConfigText]);
            return next.length > 100 ? next.slice(next.length - 100) : next;
        });
        setActiveConfigText(previousText);
    }

    function redoConfigEdit() {
        if (!configRedoStack.length) return;

        var nextText = configRedoStack[configRedoStack.length - 1];
        setConfigRedoStack(configRedoStack.slice(0, -1));
        setConfigUndoStack(function (prev) {
            var next = prev.concat([activeConfigText]);
            return next.length > 100 ? next.slice(next.length - 100) : next;
        });
        setActiveConfigText(nextText);
    }

    function applyConfigEditor() {
        try {
            var parsed = activeConfigText.trim() ? JSON.parse(activeConfigText) : {};
            setActiveConfig(parsed);
            setDryRunResult(null);
            addExecLog("config", "Configuration editee appliquee", {
                profileCount: Object.keys(parsed.profiles || {}).length,
                hasRules: !!parsed.rules
            }, "ok");
            setOk("udl_config.js applique depuis l'editeur.");
        } catch (e) {
            setError("JSON udl_config.js invalide: " + e.message);
        }
    }

    function loadTemplate(templateId) {
        var tpl = eventTemplates.find(function (t) { return t.id === templateId; });
        if (!tpl) return;
        setSelectedTemplateId(templateId);
        setEventPayloadText(pretty(tpl.payload));
    }

    function pushIntegratorEvent() {
        var payload = {};
        try {
            payload = eventPayloadText.trim() ? JSON.parse(eventPayloadText) : {};
        } catch (e) {
            setError("Payload JSON invalide: " + e.message);
            return;
        }

        window.dataLayer = window.dataLayer || [];
        window.dataLayer.push(payload);
        addExecLog("dataLayer", "Objet pousse dans dataLayer", payload, "info");

        var canManualSync = !!window.udl && typeof window.udl.syncToTagCommander === "function";
        var udlHookActive = !!window.__udlDataLayerPatched && canManualSync;

        if (!udlHookActive && canManualSync) {
            // Le runtime est charge mais pas start(): le patch dataLayer n'est pas actif.
            window.udl.syncToTagCommander(payload);
            addExecLog("sync", "Patch dataLayer UDL inactif: synchronisation UDL forcee manuellement", {
                event: payload.event || null,
                udlStarted: udlStarted
            }, "warn");
            setOk("Objet pousse + sync UDL manuelle: " + (payload.event || "(sans event)"));
            return;
        }

        if (!udlHookActive && !canManualSync) {
            addExecLog("sync", "UDL indisponible: push sans synchronisation TC", {
                event: payload.event || null,
                udlStarted: udlStarted
            }, "error");
            setError("Objet pousse dans dataLayer, mais UDL n'est pas actif (chargez puis initialisez le runtime).");
            return;
        }

        setOk("Objet integrateur pousse dans dataLayer: " + (payload.event || "(sans event)"));
    }

    function clearTraces() {
        setTraces([]);
        setExecLogs([]);
        setOk("Traces effacees.");
    }

    function getTraceTitle(entry) {
        if (entry.channel === "cact") {
            var eventName = Array.isArray(entry.payload) ? entry.payload[1] : "";
            return "Emission TMS TC (cact): " + (eventName || "-");
        }
        if (entry.channel === "script") {
            var info = entry.payload || {};
            return "Chargement script TMS (" + (info.tmsType || "other") + "): " + (info.src || "-");
        }
        var dlEvent = entry.payload && entry.payload.event ? entry.payload.event : "(sans event)";
        return "Entree dataLayer: " + dlEvent;
    }

    return (
        <>
            <div className="content-header">
                <h1 className="title">UDL Runtime Lab</h1>
                <p className="subtitle">
                    Simulez des domaines cibles, choisissez les versions de udl/ad_lib/udl_config a tester, initialisez le runtime avec la config resolue, puis injectez les objets integrateur dans dataLayer.
                </p>
                <UdlRuntimeBadges depsReady={depsReady} runtimeReady={runtimeReady} />
            </div>

            {!udlLoaded && (
                <div className="status error">window.udl n'est pas detecte. Utilisez "Charger runtime UDL".</div>
            )}

            <div className="runtime-lab-grid" style={{ marginBottom: "16px" }}>
                <UdlRuntimeLoadPanel
                    udlVersionOptions={udlVersionOptions}
                    adLibVersionOptions={adLibVersionOptions}
                    configVersionOptions={configVersionOptions}
                    selectedUdlVersionId={selectedUdlVersionId}
                    setSelectedUdlVersionId={setSelectedUdlVersionId}
                    selectedAdLibVersionId={selectedAdLibVersionId}
                    setSelectedAdLibVersionId={setSelectedAdLibVersionId}
                    selectedConfigVersionId={selectedConfigVersionId}
                    setSelectedConfigVersionId={setSelectedConfigVersionId}
                    loadUdlFile={loadUdlFile}
                    runtimeReady={runtimeReady}
                />

                <UdlRuntimeDomainPanel
                    contextTab={contextTab}
                    setContextTab={setContextTab}
                    domainPresets={domainPresets}
                    applyDomainPreset={applyDomainPreset}
                    hostInput={hostInput}
                    setHostInput={setHostInput}
                    hrefInput={hrefInput}
                    setHrefInput={setHrefInput}
                    runDomainResolution={runDomainResolution}
                    resetConfigFromWindow={resetConfigFromWindow}
                    startForResolvedDomain={startForResolvedDomain}
                    dryRunResult={dryRunResult}
                    pretty={pretty}
                    activeConfigText={activeConfigText}
                    onConfigEditorChange={onConfigEditorChange}
                    reloadConfigFromRemote={resetConfigFromWindow}
                    undoConfigEdit={undoConfigEdit}
                    redoConfigEdit={redoConfigEdit}
                    canUndo={configUndoStack.length > 0}
                    canRedo={configRedoStack.length > 0}
                    isResolving={isResolving}
                    isStarting={isStarting}
                />

                <UdlRuntimeEventPanel
                    eventTemplates={eventTemplates}
                    selectedTemplateId={selectedTemplateId}
                    loadTemplate={loadTemplate}
                    eventPayloadText={eventPayloadText}
                    setEventPayloadText={setEventPayloadText}
                    pushIntegratorEvent={pushIntegratorEvent}
                    statusType={statusType}
                    status={status}
                />
            </div>

            <section className="panel">
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "8px" }}>
                    <h3>Traces des donnees envoyees aux TMS</h3>
                    <button className="btn secondary" onClick={clearTraces}>Effacer</button>
                </div>

                <div className="trace-list" style={{ maxHeight: "440px" }}>
                    {traces.length === 0 && <div className="trace-item">Aucune trace pour le moment.</div>}
                    {traces.map(function (entry, idx) {
                        var cls = entry.channel === "cact" ? " trace-profile" : " trace-tms";
                        return (
                            <div key={idx} className={"trace-item" + cls}>
                                <strong>[{entry.at}] {getTraceTitle(entry)}</strong>
                                <div style={{ marginTop: "6px", whiteSpace: "pre-wrap" }}><strong>Payload:</strong>{"\n"}{pretty(entry.payload)}</div>
                                <div style={{ marginTop: "6px", whiteSpace: "pre-wrap" }}><strong>tc_vars snapshot:</strong>{"\n"}{pretty(entry.tcVarsSnapshot || {})}</div>
                                <div style={{ marginTop: "6px", whiteSpace: "pre-wrap" }}><strong>Config containers resolus:</strong>{"\n"}{pretty(dryRunResult && dryRunResult.config ? (dryRunResult.config.containers || []) : [])}</div>
                            </div>
                        );
                    })}
                </div>
            </section>

            <section className="panel" style={{ marginTop: "16px" }}>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "8px" }}>
                    <h3>Journal technique (progression)</h3>
                    <button className="btn secondary" onClick={function () { setExecLogs([]); }}>Effacer journal</button>
                </div>

                <div className="trace-list" style={{ maxHeight: "320px" }}>
                    {execLogs.length === 0 && <div className="trace-item">Aucun log technique pour le moment.</div>}
                    {execLogs.map(function (row, idx) {
                        var cls = row.level === "error" ? " trace-warn" : (row.level === "ok" ? " trace-tms" : "");
                        return (
                            <div key={idx} className={"trace-item" + cls}>
                                <strong>[{row.at}] {row.step}</strong> - {row.message}
                                {row.details ? <div style={{ marginTop: "6px", whiteSpace: "pre-wrap" }}>{pretty(row.details)}</div> : null}
                            </div>
                        );
                    })}
                </div>
            </section>
        </>
    );
}

function UdlPackage() {
    const ACTIVE_OPTION = {
        id: "active",
        serial: 0,
        createdAt: "",
        actor: "workspace",
        author: "workspace",
        label: "active file",
        display: "#0 | active | workspace | active file"
    };

    const [configVersions, setConfigVersions] = useState([ACTIVE_OPTION]);
    const [udlVersions, setUdlVersions] = useState([ACTIVE_OPTION]);
    const [adLibVersions, setAdLibVersions] = useState([ACTIVE_OPTION]);
    const [selectedConfigVersionId, setSelectedConfigVersionId] = useState("active");
    const [selectedUdlVersionId, setSelectedUdlVersionId] = useState("active");
    const [selectedAdLibVersionId, setSelectedAdLibVersionId] = useState("active");
    const [packageMode, setPackageMode] = useState("both");
    const [packageName, setPackageName] = useState("");
    const [lastPackage, setLastPackage] = useState(null);
    const [packageHistory, setPackageHistory] = useState([]);
    const [liveArtifacts, setLiveArtifacts] = useState([]);
    const [snapshots, setSnapshots] = useState([]);
    const [selectedSnapshotId, setSelectedSnapshotId] = useState("");
    const [snapshotDetails, setSnapshotDetails] = useState(null);
    const [status, setStatus] = useState("Pret.");
    const [statusType, setStatusType] = useState("ok");
    const [busy, setBusy] = useState(false);

    function setOk(message) {
        setStatusType("ok");
        setStatus(message);
    }

    function setError(message) {
        setStatusType("error");
        setStatus(message);
    }

    function ensureListWithActive(list) {
        var safe = Array.isArray(list) ? list.slice() : [];
        var hasActive = safe.some(function (row) { return row && row.id === "active"; });
        if (!hasActive) {
            safe.unshift(ACTIVE_OPTION);
        }
        return safe;
    }

    function refreshVersions(preferred) {
        var picks = preferred || {};
        return Promise.all([
            fetch("/api/udl-config/versions").then(function (res) { return parseApiResponseSafe(res, "/api/udl-config/versions"); }),
            fetch("/api/udl-source/version-options/udl-runtime").then(function (res) { return parseApiResponseSafe(res, "/api/udl-source/version-options/udl-runtime"); }),
            fetch("/api/udl-source/version-options/ad-lib").then(function (res) { return parseApiResponseSafe(res, "/api/udl-source/version-options/ad-lib"); })
        ]).then(function (payloads) {
            var configList = ensureListWithActive(payloads[0] && payloads[0].versions ? payloads[0].versions : []);
            var udlList = ensureListWithActive(payloads[1] && payloads[1].versions ? payloads[1].versions : []);
            var adLibList = ensureListWithActive(payloads[2] && payloads[2].versions ? payloads[2].versions : []);

            setConfigVersions(configList);
            setUdlVersions(udlList);
            setAdLibVersions(adLibList);

            var configPick = String(picks.configSnapshotId || selectedConfigVersionId || "active");
            var udlPick = String(picks.udlRevisionId || selectedUdlVersionId || "active");
            var adLibPick = String(picks.adLibRevisionId || selectedAdLibVersionId || "active");

            setSelectedConfigVersionId(configList.some(function (row) { return row.id === configPick; }) ? configPick : "active");
            setSelectedUdlVersionId(udlList.some(function (row) { return row.id === udlPick; }) ? udlPick : "active");
            setSelectedAdLibVersionId(adLibList.some(function (row) { return row.id === adLibPick; }) ? adLibPick : "active");
        });
    }

    function persistRuntimeSelection(configVersionId, udlVersionId, adLibVersionId) {
        try {
            window.__udlRuntimeLabVersionSelection = {
                configVersionId: configVersionId,
                udlVersionId: udlVersionId,
                adLibVersionId: adLibVersionId,
                updatedAt: new Date().toISOString()
            };
        } catch (e) { }
    }

    function refreshPackageHistory() {
        return fetch("/api/udl-package/history").then(function (res) {
            return parseApiResponseSafe(res, "/api/udl-package/history");
        }).then(function (payload) {
            setPackageHistory(Array.isArray(payload.history) ? payload.history : []);
        });
    }

    function refreshLiveArtifacts() {
        return fetch("/api/live-artifacts").then(function (res) {
            return parseApiResponseSafe(res, "/api/live-artifacts");
        }).then(function (payload) {
            setLiveArtifacts(Array.isArray(payload.files) ? payload.files : []);
        });
    }

    function refreshSnapshots(preferredId) {
        return fetch("/api/project-snapshots")
            .then(function (res) {
                return parseApiResponseSafe(res, "/api/project-snapshots");
            })
            .then(function (payload) {
                var list = Array.isArray(payload.snapshots) ? payload.snapshots : [];
                setSnapshots(list);

                if (list.length === 0) {
                    setSelectedSnapshotId("");
                    setSnapshotDetails(null);
                    return "";
                }

                var candidate = preferredId || selectedSnapshotId || list[0].id;
                var exists = list.some(function (entry) { return entry.id === candidate; });
                var nextId = exists ? candidate : list[0].id;
                setSelectedSnapshotId(nextId);
                return nextId;
            });
    }

    function loadSnapshotDetails(snapshotId) {
        if (!snapshotId) {
            setSnapshotDetails(null);
            return Promise.resolve();
        }

        return fetch("/api/project-snapshots/" + encodeURIComponent(snapshotId))
            .then(function (res) {
                return parseApiResponseSafe(res, "/api/project-snapshots/:id");
            })
            .then(function (payload) {
                setSnapshotDetails(payload.snapshot || null);
            });
    }

    function restoreSnapshot() {
        if (!selectedSnapshotId) {
            setError("Aucun snapshot selectionne.");
            return;
        }

        if (!window.confirm("Restaurer ce snapshot projet ? Un backup automatique sera cree avant restauration.")) {
            return;
        }

        setBusy(true);
        fetch("/api/project-snapshots/" + encodeURIComponent(selectedSnapshotId) + "/restore", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({})
        }).then(function (res) {
            return parseApiResponseSafe(res, "/api/project-snapshots/:id/restore");
        }).then(function (payload) {
            var out = payload && payload.output ? payload.output : {};
            return refreshSnapshots(selectedSnapshotId).then(function () {
                return loadSnapshotDetails(selectedSnapshotId);
            }).then(function () {
                setOk("Restore termine. Fichiers restaures: " + (out.restoredFiles || 0) + ". Backup: " + (out.backupSnapshotId || "n/a"));
            });
        }).catch(function (e) {
            setError("Restore impossible: " + e.message);
        }).finally(function () {
            setBusy(false);
        });
    }

    useEffect(function () {
        refreshVersions().catch(function (e) {
            setError("Impossible de charger les versions: " + e.message);
        });
        refreshPackageHistory().catch(function (e) {
            setError("Impossible de charger l'historique des packages: " + e.message);
        });
        refreshLiveArtifacts().catch(function (e) {
            setError("Impossible de charger les fichiers actifs: " + e.message);
        });
        refreshSnapshots().then(function (id) {
            if (id) return loadSnapshotDetails(id);
            return null;
        }).catch(function (e) {
            setError("Impossible de charger les snapshots: " + e.message);
        });
    }, []);

    useEffect(function () {
        persistRuntimeSelection(selectedConfigVersionId, selectedUdlVersionId, selectedAdLibVersionId);
    }, [selectedConfigVersionId, selectedUdlVersionId, selectedAdLibVersionId]);

    useEffect(function () {
        if (!selectedSnapshotId) {
            setSnapshotDetails(null);
            return;
        }

        loadSnapshotDetails(selectedSnapshotId).catch(function (e) {
            setError("Chargement detail impossible: " + e.message);
        });
    }, [selectedSnapshotId]);

    function createPackage() {
        setBusy(true);
        fetch("/api/udl-package", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
                configSnapshotId: selectedConfigVersionId || "active",
                udlRevisionId: selectedUdlVersionId || "active",
                adLibRevisionId: selectedAdLibVersionId || "active",
                mode: packageMode,
                packageName: packageName
            })
        }).then(function (res) {
            return parseApiResponseSafe(res, "/api/udl-package");
        }).then(function (payload) {
            var output = payload.output || null;
            setLastPackage(output);
            return Promise.all([
                refreshPackageHistory(),
                refreshSnapshots(output && output.snapshotId ? output.snapshotId : "")
            ]).then(function () {
                if (output && output.snapshotId) {
                    return loadSnapshotDetails(output.snapshotId);
                }
                return null;
            }).then(function () {
                setOk("Package genere: " + ((output && output.zip) || "ok") + (output && output.snapshotId ? " (snapshot " + output.snapshotId + " cree)" : ""));
            });
        }).catch(function (e) {
            setError("Packaging impossible: " + e.message);
        }).finally(function () {
            setBusy(false);
        });
    }

    return (
        <>
            <div className="content-header">
                <h1 className="title">UDL Package</h1>
                <p className="subtitle">
                    Selectionnez les versions de `udl`, `ad_lib` et `udl_config` puis composez un package. Le versioning de `udl_config` est gere dans l'annexe de UDL Config Debug.
                </p>
            </div>

            <section className="panel runtime-step-panel" style={{ marginBottom: "16px" }}>
                <div className="panel-kicker">Fichiers actifs</div>
                <h3>URLs d'integration (build courant dans dist/)</h3>
                <p className="subtitle compact">
                    Ces URLs pointent vers le dernier build genere (`npm run build` / `npm run build:prod`), pas vers un package specifique. Utilisez-les pour integrer udl.js ou udl_uat.js sur un site.
                </p>
                <div className="actions" style={{ marginTop: "8px" }}>
                    <button className="btn secondary" onClick={function () { refreshLiveArtifacts().then(function () { setOk("Fichiers actifs recharges."); }).catch(function (e) { setError("Erreur rechargement fichiers actifs: " + e.message); }); }} disabled={busy}>Rafraichir</button>
                </div>
                <div className="trace-list" style={{ marginTop: "8px" }}>
                    {liveArtifacts.map(function (file) {
                        var absoluteUrl = file.exists ? (window.location.origin + file.url) : "";
                        return (
                            <div key={file.id} className="trace-item">
                                <strong>{file.label}:</strong>{" "}
                                {file.exists ? (
                                    <>
                                        <a href={file.url} target="_blank" rel="noreferrer">{absoluteUrl}</a>
                                        {" "}({file.size} o, maj {file.modifiedAt})
                                    </>
                                ) : (
                                    <span>absent - lancez un build (`npm run build` / `npm run build:prod`)</span>
                                )}
                            </div>
                        );
                    })}
                </div>
            </section>

            <section className="panel runtime-step-panel" style={{ marginBottom: "16px" }}>
                <div className="panel-kicker">Etape 1</div>
                <h3>Selection des versions pour le package</h3>
                <div className="actions" style={{ marginTop: "8px" }}>
                    <button className="btn secondary" onClick={function () { refreshVersions().then(function () { setOk("Versions rechargees."); }).catch(function (e) { setError("Erreur rechargement versions: " + e.message); }); }} disabled={busy}>Rafraichir</button>
                </div>
                <div className="udl-manager-grid" style={{ marginTop: "8px" }}>
                    <div className="field">
                        <label className="label">Version udl</label>
                        <select value={selectedUdlVersionId} onChange={function (e) { setSelectedUdlVersionId(e.target.value); }}>
                            {udlVersions.map(function (v) {
                                return <option key={v.id} value={v.id}>{v.display || v.id}</option>;
                            })}
                        </select>
                    </div>

                    <div className="field">
                        <label className="label">Version ad_lib</label>
                        <select value={selectedAdLibVersionId} onChange={function (e) { setSelectedAdLibVersionId(e.target.value); }}>
                            {adLibVersions.map(function (v) {
                                return <option key={v.id} value={v.id}>{v.display || v.id}</option>;
                            })}
                        </select>
                    </div>

                    <div className="field">
                        <label className="label">Version udl_config</label>
                        <select value={selectedConfigVersionId} onChange={function (e) { setSelectedConfigVersionId(e.target.value); }}>
                            {configVersions.map(function (v) {
                                return <option key={v.id} value={v.id}>{v.display || v.id}</option>;
                            })}
                        </select>
                        <div className="field-hint">Format simplifie: index, date, auteur, libelle.</div>
                    </div>
                </div>
            </section>

            <section className="panel runtime-step-panel">
                <div className="panel-kicker">Etape 2</div>
                <h3>Packaging</h3>
                <div className="udl-manager-grid">
                    <div className="field">
                        <label className="label">Mode de package</label>
                        <select value={packageMode} onChange={function (e) { setPackageMode(e.target.value); }}>
                            <option value="both">both (udl.js + udl_uat.js + ad_lib.js + udl_config.js)</option>
                            <option value="bundle">bundle (udl.js seulement)</option>
                            <option value="autonomous">autonomous (udl_uat.js + ad_lib.js + udl_config.js)</option>
                        </select>
                    </div>
                    <div className="field">
                        <label className="label">Nom de package (optionnel)</label>
                        <input value={packageName} onChange={function (e) { setPackageName(e.target.value); }} placeholder="ex: udl_release_particulier_uat" />
                    </div>
                </div>
                <div className="actions">
                    <button className="btn" onClick={createPackage} disabled={busy}>Generer le package</button>
                </div>

                {lastPackage ? (
                    <div className="trace-list" style={{ marginTop: "12px" }}>
                        <div className="trace-item"><strong>Mode:</strong> {lastPackage.mode}</div>
                        <div className="trace-item"><strong>Dossier:</strong> {lastPackage.folder}</div>
                        <div className="trace-item"><strong>ZIP:</strong> {lastPackage.zip}</div>
                        <div className="trace-item"><strong>Manifest:</strong> {lastPackage.manifest}</div>
                        {lastPackage.selectedVersions ? (
                            <div className="trace-item">
                                <strong>Versions packagees:</strong>
                                <div style={{ marginTop: "6px", whiteSpace: "pre-wrap" }}>{pretty(lastPackage.selectedVersions)}</div>
                            </div>
                        ) : null}
                        {lastPackage.downloadUrl ? (
                            <div className="trace-item">
                                <strong>Telechargement:</strong> <a href={lastPackage.downloadUrl} target="_blank" rel="noreferrer">ouvrir le zip</a>
                            </div>
                        ) : null}
                        {Array.isArray(lastPackage.files) && lastPackage.files.length > 0 ? (
                            <div className="trace-item">
                                <strong>Fichiers accessibles par URL:</strong>
                                <div style={{ marginTop: "6px" }}>
                                    {lastPackage.files.map(function (f) {
                                        return (
                                            <div key={f.path}>
                                                <a href={f.url} target="_blank" rel="noreferrer">{window.location.origin + f.url}</a>
                                            </div>
                                        );
                                    })}
                                </div>
                            </div>
                        ) : null}
                        {lastPackage.snapshotId ? (
                            <div className="trace-item"><strong>Snapshot associe:</strong> {lastPackage.snapshotId}</div>
                        ) : null}
                    </div>
                ) : null}

                <div className={"status " + statusType} style={{ marginTop: "12px" }}>{status}</div>
            </section>

            <section className="panel runtime-step-panel" style={{ marginTop: "16px" }}>
                <div className="panel-kicker">Etape 3</div>
                <h3>Historique des packages generes</h3>
                <div className="actions" style={{ marginTop: "8px" }}>
                    <button className="btn secondary" onClick={function () { refreshPackageHistory().catch(function (e) { setError("Rafraichissement impossible: " + e.message); }); }} disabled={busy}>Rafraichir</button>
                </div>
                {packageHistory.length === 0 ? (
                    <div className="trace-item" style={{ marginTop: "8px" }}>Aucun package genere pour le moment.</div>
                ) : (
                    <div className="trace-list" style={{ marginTop: "8px", maxHeight: "300px" }}>
                        {packageHistory.map(function (entry) {
                            return (
                                <div key={entry.id} className="trace-item">
                                    <div>
                                        <strong>{entry.packageName}</strong> ({entry.mode}) - {entry.createdAt}
                                        {entry.snapshotId ? " - snapshot " + entry.snapshotId : ""}
                                        {entry.downloadUrl ? (
                                            <> - <a href={entry.downloadUrl} target="_blank" rel="noreferrer">zip</a></>
                                        ) : null}
                                    </div>
                                    {Array.isArray(entry.files) && entry.files.length > 0 ? (
                                        <div style={{ marginTop: "4px", marginLeft: "12px" }}>
                                            {entry.files.map(function (f) {
                                                return (
                                                    <div key={f.path}>
                                                        <a href={f.url} target="_blank" rel="noreferrer">{window.location.origin + f.url}</a>
                                                    </div>
                                                );
                                            })}
                                        </div>
                                    ) : null}
                                </div>
                            );
                        })}
                    </div>
                )}
            </section>

            <section className="panel runtime-step-panel" style={{ marginTop: "16px" }}>
                <div className="panel-kicker">Etape 4</div>
                <h3>Snapshots UDL</h3>
                <p className="subtitle compact">
                    Un snapshot des fichiers UDL critiques (udl, ad_lib, udl_config, build UAT) est cree automatiquement a chaque package genere. Utilisez cette liste pour consulter le detail ou restaurer un etat anterieur.
                </p>
                <div className="actions" style={{ marginTop: "8px" }}>
                    <button
                        className="btn secondary"
                        onClick={function () {
                            refreshSnapshots(selectedSnapshotId)
                                .then(function (id) {
                                    if (!id) return null;
                                    return loadSnapshotDetails(id);
                                })
                                .then(function () {
                                    setOk("Liste des snapshots rechargee.");
                                })
                                .catch(function (e) {
                                    setError("Rafraichissement impossible: " + e.message);
                                });
                        }}
                        disabled={busy}
                    >
                        Rafraichir
                    </button>
                    <button className="btn secondary" onClick={restoreSnapshot} disabled={busy || !selectedSnapshotId}>Restaurer le snapshot selectionne</button>
                </div>

                <div className="field" style={{ marginTop: "8px" }}>
                    <label className="label">Snapshot cible</label>
                    <select value={selectedSnapshotId} onChange={function (e) { setSelectedSnapshotId(e.target.value); }} disabled={busy || snapshots.length === 0}>
                        {snapshots.length === 0 ? <option value="">Aucun snapshot</option> : null}
                        {snapshots.map(function (entry) {
                            var optionLabel = entry.id + " | " + (entry.label || "sans label") + " | " + (entry.fileCount || 0) + " fichier(s)";
                            return <option key={entry.id} value={entry.id}>{optionLabel}</option>;
                        })}
                    </select>
                </div>

                {snapshotDetails ? (
                    <div className="trace-list" style={{ marginTop: "12px" }}>
                        <div className="trace-item"><strong>ID:</strong> {snapshotDetails.id}</div>
                        <div className="trace-item"><strong>Label:</strong> {snapshotDetails.label || "-"}</div>
                        <div className="trace-item"><strong>Source:</strong> {snapshotDetails.source || "-"}</div>
                        <div className="trace-item"><strong>Cree le:</strong> {snapshotDetails.createdAt}</div>
                        <div className="trace-item"><strong>Fichiers:</strong> {snapshotDetails.fileCount || 0}</div>
                    </div>
                ) : (
                    <div className="trace-item" style={{ marginTop: "12px" }}>Aucun detail charge.</div>
                )}
            </section>
        </>
    );
}

function UdlSourceEditor() {
    const [files, setFiles] = useState([]);
    const [selectedId, setSelectedId] = useState("");
    const [sourceText, setSourceText] = useState("");
    const [baseText, setBaseText] = useState("");
    const [fileMeta, setFileMeta] = useState(null);
    const [savedSha, setSavedSha] = useState("");
    const [actor, setActor] = useState("backoffice");
    const [changeReason, setChangeReason] = useState("");
    const [history, setHistory] = useState([]);
    const [compareFromId, setCompareFromId] = useState("");
    const [compareToId, setCompareToId] = useState("");
    const [diffResult, setDiffResult] = useState(null);
    const [busy, setBusy] = useState(false);
    const [status, setStatus] = useState("Pret.");
    const [statusType, setStatusType] = useState("ok");
    const dirty = sourceText !== baseText;

    function setOk(message) {
        setStatusType("ok");
        setStatus(message);
    }

    function setError(message) {
        setStatusType("error");
        setStatus(message);
    }

    function refreshFiles(preferredId) {
        return fetch("/api/udl-source/files")
            .then(function (res) {
                return parseApiResponseSafe(res, "/api/udl-source/files");
            })
            .then(function (payload) {
                var list = Array.isArray(payload.files) ? payload.files : [];
                setFiles(list);

                if (list.length === 0) {
                    setSelectedId("");
                    setFileMeta(null);
                    setSourceText("");
                    setBaseText("");
                    setSavedSha("");
                    setOk("Aucune source UDL disponible.");
                    return "";
                }

                var candidate = preferredId || selectedId || list[0].id;
                var exists = list.some(function (f) { return f.id === candidate; });
                var nextId = exists ? candidate : list[0].id;

                if (nextId !== selectedId) {
                    setSelectedId(nextId);
                }

                return nextId;
            });
    }

    function refreshHistory(sourceId) {
        if (!sourceId) {
            setHistory([]);
            setCompareFromId("");
            setCompareToId("");
            setDiffResult(null);
            return Promise.resolve();
        }

        return fetch("/api/udl-source/history/" + encodeURIComponent(sourceId) + "?limit=80")
            .then(function (res) {
                return parseApiResponseSafe(res, "/api/udl-source/history/:id");
            })
            .then(function (payload) {
                var list = Array.isArray(payload.history) ? payload.history : [];
                setHistory(list);

                var hasFrom = compareFromId && list.some(function (row) { return row.id === compareFromId; });
                var hasTo = compareToId && list.some(function (row) { return row.id === compareToId; });

                if (!hasTo) {
                    setCompareToId(list.length > 0 ? list[0].id : "");
                }

                if (!hasFrom) {
                    if (list.length > 1) setCompareFromId(list[1].id);
                    else if (list.length > 0) setCompareFromId(list[0].id);
                    else setCompareFromId("");
                }
            });
    }

    function runDiff() {
        if (!selectedId) {
            setError("Aucune source selectionnee.");
            return;
        }
        if (!compareFromId || !compareToId) {
            setError("Selectionnez deux revisions a comparer.");
            return;
        }

        setBusy(true);
        fetch(
            "/api/udl-source/history/" +
            encodeURIComponent(selectedId) +
            "/diff?from=" + encodeURIComponent(compareFromId) +
            "&to=" + encodeURIComponent(compareToId) +
            "&limit=300"
        ).then(function (res) {
            return parseApiResponseSafe(res, "/api/udl-source/history/:id/diff");
        }).then(function (payload) {
            setDiffResult(payload || null);
            var stats = payload && payload.diff && payload.diff.stats ? payload.diff.stats : null;
            setOk("Diff calcule" + (stats ? (" | changements: " + stats.totalChanges) : ""));
        }).catch(function (e) {
            setError("Diff impossible: " + e.message);
        }).finally(function () {
            setBusy(false);
        });
    }

    function loadFile(sourceId) {
        if (!sourceId) return Promise.resolve();

        setBusy(true);
        return fetch("/api/udl-source/file/" + encodeURIComponent(sourceId))
            .then(function (res) {
                return parseApiResponseSafe(res, "/api/udl-source/file/:id");
            })
            .then(function (payload) {
                var nextText = typeof payload.content === "string" ? payload.content : "";
                setSourceText(nextText);
                setBaseText(nextText);
                setFileMeta(payload.file || null);
                setSavedSha(String(payload.sha256 || ""));
                setOk("Source chargee: " + ((payload.file && payload.file.label) || sourceId));
                return refreshHistory(sourceId);
            })
            .catch(function (e) {
                setError("Chargement impossible: " + e.message);
            })
            .finally(function () {
                setBusy(false);
            });
    }

    function saveFile() {
        if (!selectedId) {
            setError("Aucune source selectionnee.");
            return;
        }

        if (!String(changeReason || "").trim()) {
            setError("Motif de modification obligatoire.");
            return;
        }

        setBusy(true);
        fetch("/api/udl-source/file/" + encodeURIComponent(selectedId), {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
                content: sourceText,
                actor: actor,
                reason: changeReason,
                expectedSha256: savedSha
            })
        }).then(function (res) {
            return parseApiResponseSafe(res, "/api/udl-source/file/:id");
        }).then(function (payload) {
            setBaseText(sourceText);
            setSavedSha(String(payload.sha256 || ""));
            setChangeReason("");
            return refreshFiles(selectedId).then(function () {
                return refreshHistory(selectedId);
            }).then(function () {
                var revisionId = payload && payload.revision && payload.revision.id ? payload.revision.id : "n/a";
                setOk("Sauvegarde OK: " + ((payload.file && payload.file.path) || selectedId) + " | revision " + revisionId);
            });
        }).catch(function (e) {
            setError("Sauvegarde impossible: " + e.message);
        }).finally(function () {
            setBusy(false);
        });
    }

    function restoreRevision(revisionId) {
        if (!selectedId || !revisionId) return;
        if (!String(changeReason || "").trim()) {
            setError("Motif de restauration obligatoire.");
            return;
        }

        if (!window.confirm("Restaurer cette revision dans le fichier courant ?")) {
            return;
        }

        setBusy(true);
        fetch("/api/udl-source/history/" + encodeURIComponent(selectedId) + "/" + encodeURIComponent(revisionId) + "/restore", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
                actor: actor,
                reason: changeReason
            })
        }).then(function (res) {
            return parseApiResponseSafe(res, "/api/udl-source/history/:id/:revisionId/restore");
        }).then(function (payload) {
            setChangeReason("");
            return loadFile(selectedId).then(function () {
                var restoredFrom = payload && payload.restoredFromRevisionId ? payload.restoredFromRevisionId : revisionId;
                setOk("Restauration OK depuis revision " + restoredFrom + ".");
            });
        }).catch(function (e) {
            setError("Restauration impossible: " + e.message);
        }).finally(function () {
            setBusy(false);
        });
    }

    function onSelectChange(event) {
        var nextId = event.target.value;
        if (nextId === selectedId) return;
        if (dirty && !window.confirm("Des modifications non sauvegardees seront perdues. Continuer ?")) {
            return;
        }
        setSelectedId(nextId);
    }

    useEffect(function () {
        var cancelled = false;
        var delays = [0, 400, 1200];

        function tryBootstrap(attemptIndex) {
            if (cancelled) return;

            refreshFiles().then(function (id) {
                if (!id) {
                    throw new Error("Aucune source detectee.");
                }
            }).catch(function (e) {
                if (cancelled) return;

                if (attemptIndex < delays.length - 1) {
                    window.setTimeout(function () {
                        tryBootstrap(attemptIndex + 1);
                    }, delays[attemptIndex + 1]);
                    return;
                }

                setError("Impossible de charger la liste des sources: " + e.message);
            });
        }

        tryBootstrap(0);

        return function () {
            cancelled = true;
        };
    }, []);

    useEffect(function () {
        function ensureLoadedIfNeeded() {
            if (selectedId) return;
            refreshFiles().catch(function () { });
        }

        function onVisibilityChange() {
            if (document.visibilityState === "visible") {
                ensureLoadedIfNeeded();
            }
        }

        window.addEventListener("focus", ensureLoadedIfNeeded);
        document.addEventListener("visibilitychange", onVisibilityChange);

        return function () {
            window.removeEventListener("focus", ensureLoadedIfNeeded);
            document.removeEventListener("visibilitychange", onVisibilityChange);
        };
    }, [selectedId]);

    useEffect(function () {
        if (!selectedId) return;
        loadFile(selectedId);
    }, [selectedId]);

    function shortSha(value) {
        var str = String(value || "");
        if (!str) return "-";
        if (str.length <= 16) return str;
        return str.slice(0, 8) + "..." + str.slice(-8);
    }

    function formatRevisionDisplay(row) {
        if (!row) return "-";
        if (row.display) return row.display;

        var serial = Number(row.serial) || 0;
        var createdAt = String(row.createdAt || "");
        var actor = String(row.actor || "inconnu");
        var label = String(row.reason || row.label || "").trim();
        return "#" + serial + " | " + createdAt + " | " + actor + (label ? " | " + label : "");
    }

    function renderDiffCell(line, side) {
        var isLeft = side === "left";
        var text = isLeft ? line.before : line.after;
        var className = "source-diff-cell";

        if (line.type === "added") {
            className += isLeft ? " empty" : " added";
        } else if (line.type === "removed") {
            className += isLeft ? " removed" : " empty";
        } else {
            className += " changed";
        }

        return (
            <div className={className}>
                <div className="source-diff-line-header">L{line.line}</div>
                <pre>{text || ""}</pre>
            </div>
        );
    }

    return (
        <>
            <div className="content-header">
                <h1 className="title">UDL Source Editor</h1>
                <p className="subtitle">
                    Chargez, editez et sauvegardez les sources UDL, avec historique et comparaison de revisions.
                </p>
            </div>

            <section className="panel source-editor-panel" style={{ marginBottom: "14px" }}>
                <div className="source-toolbar">
                    <div className="field" style={{ marginBottom: 0 }}>
                        <label className="label">Source</label>
                        <select value={selectedId} onChange={onSelectChange} disabled={busy || files.length === 0}>
                            {files.length === 0 ? <option value="">Aucune source</option> : null}
                            {files.map(function (f) {
                                return <option key={f.id} value={f.id}>{f.label}</option>;
                            })}
                        </select>
                    </div>
                    <div className="actions source-actions" style={{ margin: 0 }}>
                        <button className="btn secondary" onClick={function () { refreshFiles(selectedId).catch(function (e) { setError("Erreur de rafraichissement: " + e.message); }); }} disabled={busy}>Rafraichir</button>
                        <button className="btn secondary" onClick={function () { loadFile(selectedId); }} disabled={busy || !selectedId}>Recharger</button>
                        <button className="btn secondary" onClick={function () { refreshHistory(selectedId).catch(function (e) { setError("Erreur historique: " + e.message); }); }} disabled={busy || !selectedId}>Historique</button>
                        <button className="btn" onClick={saveFile} disabled={busy || !selectedId || !dirty}>Sauvegarder</button>
                    </div>
                </div>

                <div className="source-toolbar source-toolbar-secondary">
                    <div className="field" style={{ marginBottom: 0 }}>
                        <label className="label">Auteur</label>
                        <input value={actor} onChange={function (e) { setActor(e.target.value); }} placeholder="prenom.nom" />
                    </div>
                    <div className="field" style={{ marginBottom: 0 }}>
                        <label className="label">Motif (obligatoire)</label>
                        <input value={changeReason} onChange={function (e) { setChangeReason(e.target.value); }} placeholder="correction mapping event checkout" />
                    </div>
                </div>

                {fileMeta ? (
                    <details className="source-meta-details">
                        <summary>Infos source</summary>
                        <div className="source-meta-inline">
                            <span><strong>Chemin:</strong> {fileMeta.path}</span>
                            <span><strong>Taille:</strong> {fileMeta.size} o</span>
                            <span><strong>Date:</strong> {fileMeta.updatedAt}</span>
                            <span><strong>SHA:</strong> {shortSha(savedSha)}</span>
                        </div>
                    </details>
                ) : null}

                <CodeEditor
                    language="javascript"
                    value={sourceText}
                    onChange={setSourceText}
                    height={700}
                />

                <div className={"status " + statusType} style={{ marginTop: "12px" }}>
                    {status}{dirty ? " (modifications non sauvegardees)" : ""}
                </div>
            </section>

            <section className="panel source-history-panel">
                <h3>Historique et comparaison</h3>
                <div className="source-compare-toolbar">
                    <div className="field">
                        <label className="label">Revision de depart</label>
                        <select value={compareFromId} onChange={function (e) { setCompareFromId(e.target.value); }} disabled={busy || history.length === 0}>
                            {history.length === 0 ? <option value="">Aucune revision</option> : null}
                            {history.map(function (row) {
                                return <option key={row.id} value={row.id}>{formatRevisionDisplay(row)}</option>;
                            })}
                        </select>
                    </div>
                    <div className="field">
                        <label className="label">Revision cible</label>
                        <select value={compareToId} onChange={function (e) { setCompareToId(e.target.value); }} disabled={busy || history.length === 0}>
                            {history.length === 0 ? <option value="">Aucune revision</option> : null}
                            {history.map(function (row) {
                                return <option key={row.id} value={row.id}>{formatRevisionDisplay(row)}</option>;
                            })}
                        </select>
                    </div>
                    <div className="actions" style={{ margin: 0 }}>
                        <button className="btn secondary" type="button" onClick={runDiff} disabled={busy || !compareFromId || !compareToId}>Comparer</button>
                    </div>
                </div>

                {diffResult && diffResult.diff && diffResult.diff.stats ? (
                    <div className="source-diff-stats">
                        <span>Ajouts: {diffResult.diff.stats.added}</span>
                        <span>Suppressions: {diffResult.diff.stats.removed}</span>
                        <span>Modifications: {diffResult.diff.stats.changed}</span>
                        <span>Total: {diffResult.diff.stats.totalChanges}</span>
                    </div>
                ) : null}

                {diffResult && diffResult.diff && Array.isArray(diffResult.diff.lines) && diffResult.diff.lines.length > 0 ? (
                    <div className="source-diff-table" style={{ marginBottom: "10px" }}>
                        {diffResult.diff.lines.map(function (line, idx) {
                            return (
                                <div key={idx} className="source-diff-row">
                                    {renderDiffCell(line, "left")}
                                    {renderDiffCell(line, "right")}
                                </div>
                            );
                        })}
                    </div>
                ) : null}

                <div className="trace-list" style={{ maxHeight: "320px" }}>
                    {history.length === 0 ? <div className="trace-item">Aucune revision enregistree pour cette source.</div> : null}
                    {history.map(function (row) {
                        return (
                            <div key={row.id} className="trace-item">
                                <div><strong>{formatRevisionDisplay(row)}</strong></div>
                                <div style={{ marginTop: "4px" }}><strong>ID revision:</strong> {row.id}</div>
                                <div style={{ marginTop: "4px" }}><strong>Auteur:</strong> {row.actor || "-"}</div>
                                <div style={{ marginTop: "4px" }}><strong>Motif:</strong> {row.reason || "-"}</div>
                                <div style={{ marginTop: "4px" }}><strong>SHA:</strong> {shortSha(row.previousSha256)} -> {shortSha(row.currentSha256)}</div>
                                <div className="actions" style={{ marginTop: "8px" }}>
                                    <button className="btn secondary" type="button" onClick={function () { restoreRevision(row.id); }} disabled={busy || !selectedId}>Restaurer cette revision</button>
                                </div>
                            </div>
                        );
                    })}
                </div>
            </section>
        </>
    );
}

function LoginScreen(props) {
    const [devName, setDevName] = useState("");
    const [devEmail, setDevEmail] = useState("");
    const [devRole, setDevRole] = useState("reader");

    return (
        <div className="app-shell" style={{ gridTemplateColumns: "1fr" }}>
            <main className="content" style={{ maxWidth: "760px", margin: "0 auto" }}>
                <div className="content-header">
                    <h1 className="title">Connexion requise</h1>
                    <p className="subtitle">
                        Authentifiez-vous pour acceder au Back Office UDL. Les droits sont geres par role: lecteur, editeur, admin.
                    </p>
                </div>

                <section className="panel runtime-step-panel" style={{ marginBottom: "16px" }}>
                    <div className="panel-kicker">OAuth</div>
                    <h3>Connexion SSO</h3>
                    <div className="actions" style={{ marginTop: "8px" }}>
                        {props.providers.google ? (
                            <a className="btn" href="/auth/google">Se connecter avec Google</a>
                        ) : (
                            <button className="btn secondary" type="button" disabled>Google non configure</button>
                        )}
                        {props.providers.facebook ? (
                            <a className="btn" href="/auth/facebook">Se connecter avec Facebook</a>
                        ) : (
                            <button className="btn secondary" type="button" disabled>Facebook non configure</button>
                        )}
                    </div>
                </section>

                {props.providers.dev ? (
                    <section className="panel runtime-step-panel">
                        <div className="panel-kicker">Dev</div>
                        <h3>Connexion locale de test</h3>
                        <p className="subtitle compact">Reserve a un environnement de developpement (ENABLE_DEV_AUTH=true).</p>

                        <div className="udl-manager-grid" style={{ marginTop: "8px" }}>
                            <div className="field">
                                <label className="label">Nom</label>
                                <input value={devName} onChange={function (e) { setDevName(e.target.value); }} placeholder="ex: QA User" />
                            </div>
                            <div className="field">
                                <label className="label">Email</label>
                                <input value={devEmail} onChange={function (e) { setDevEmail(e.target.value); }} placeholder="ex: qa.user@domain.tld" />
                            </div>
                            <div className="field">
                                <label className="label">Role</label>
                                <select value={devRole} onChange={function (e) { setDevRole(e.target.value); }}>
                                    <option value="reader">reader</option>
                                    <option value="editor">editor</option>
                                    <option value="admin">admin</option>
                                </select>
                            </div>
                        </div>

                        <div className="actions">
                            <button
                                className="btn"
                                onClick={function () {
                                    props.onDevLogin({
                                        name: devName,
                                        email: devEmail,
                                        role: devRole
                                    });
                                }}
                            >
                                Connexion dev
                            </button>
                        </div>
                    </section>
                ) : null}

                {props.error ? <div className="status error" style={{ marginTop: "12px" }}>{props.error}</div> : null}
            </main>
        </div>
    );
}

function UsersAdmin() {
    const [users, setUsers] = useState([]);
    const [newName, setNewName] = useState("");
    const [newEmail, setNewEmail] = useState("");
    const [newRole, setNewRole] = useState("reader");
    const [status, setStatus] = useState("Pret.");
    const [statusType, setStatusType] = useState("ok");
    const [busy, setBusy] = useState(false);

    function setOk(message) {
        setStatusType("ok");
        setStatus(message);
    }

    function setError(message) {
        setStatusType("error");
        setStatus(message);
    }

    function refreshUsers() {
        return fetch("/api/users")
            .then(function (res) { return parseApiResponseSafe(res, "/api/users"); })
            .then(function (payload) {
                setUsers(Array.isArray(payload.users) ? payload.users : []);
            });
    }

    function createUser() {
        if (!newEmail.trim()) {
            setError("Email requis pour creer un utilisateur.");
            return;
        }

        setBusy(true);
        fetch("/api/users", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
                name: newName,
                email: newEmail,
                role: newRole
            })
        }).then(function (res) {
            return parseApiResponseSafe(res, "/api/users");
        }).then(function () {
            setNewName("");
            setNewEmail("");
            setNewRole("reader");
            return refreshUsers();
        }).then(function () {
            setOk("Utilisateur cree/mis a jour.");
        }).catch(function (e) {
            setError("Creation utilisateur impossible: " + e.message);
        }).finally(function () {
            setBusy(false);
        });
    }

    function updateUserRole(userId, role) {
        setBusy(true);
        fetch("/api/users/" + encodeURIComponent(userId), {
            method: "PATCH",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ role: role })
        }).then(function (res) {
            return parseApiResponseSafe(res, "/api/users/:id");
        }).then(function () {
            return refreshUsers();
        }).then(function () {
            setOk("Role utilisateur mis a jour.");
        }).catch(function (e) {
            setError("Mise a jour role impossible: " + e.message);
        }).finally(function () {
            setBusy(false);
        });
    }

    function updateUserStatus(userId, statusValue) {
        setBusy(true);
        fetch("/api/users/" + encodeURIComponent(userId), {
            method: "PATCH",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ status: statusValue })
        }).then(function (res) {
            return parseApiResponseSafe(res, "/api/users/:id");
        }).then(function () {
            return refreshUsers();
        }).then(function () {
            setOk("Statut utilisateur mis a jour.");
        }).catch(function (e) {
            setError("Mise a jour statut impossible: " + e.message);
        }).finally(function () {
            setBusy(false);
        });
    }

    useEffect(function () {
        refreshUsers().catch(function (e) {
            setError("Chargement utilisateurs impossible: " + e.message);
        });
    }, []);

    return (
        <>
            <div className="content-header">
                <h1 className="title">Utilisateurs</h1>
                <p className="subtitle">Administrez les comptes, roles et statuts d'acces.</p>
            </div>

            <section className="panel runtime-step-panel" style={{ marginBottom: "16px" }}>
                <div className="panel-kicker">Etape 1</div>
                <h3>Creer / inviter un utilisateur</h3>
                <div className="udl-manager-grid" style={{ marginTop: "8px" }}>
                    <div className="field">
                        <label className="label">Nom</label>
                        <input value={newName} onChange={function (e) { setNewName(e.target.value); }} placeholder="ex: Jane Doe" />
                    </div>
                    <div className="field">
                        <label className="label">Email</label>
                        <input value={newEmail} onChange={function (e) { setNewEmail(e.target.value); }} placeholder="ex: jane@domain.tld" />
                    </div>
                    <div className="field">
                        <label className="label">Role initial</label>
                        <select value={newRole} onChange={function (e) { setNewRole(e.target.value); }}>
                            <option value="reader">reader</option>
                            <option value="editor">editor</option>
                            <option value="admin">admin</option>
                        </select>
                    </div>
                </div>
                <div className="actions">
                    <button className="btn" onClick={createUser} disabled={busy}>Creer / mettre a jour</button>
                    <button className="btn secondary" onClick={function () { refreshUsers().then(function () { setOk("Liste utilisateurs rechargee."); }).catch(function (e) { setError("Rafraichissement impossible: " + e.message); }); }} disabled={busy}>Rafraichir</button>
                </div>
            </section>

            <section className="panel runtime-step-panel">
                <div className="panel-kicker">Etape 2</div>
                <h3>Liste des utilisateurs</h3>
                <table className="debug-table" style={{ width: "100%", marginTop: "10px" }}>
                    <thead>
                        <tr>
                            <th className="dt-key">Nom</th>
                            <th className="dt-key">Email</th>
                            <th className="dt-key">Provider</th>
                            <th className="dt-key">Role</th>
                            <th className="dt-key">Statut</th>
                            <th className="dt-key">Actions</th>
                        </tr>
                    </thead>
                    <tbody>
                        {users.length === 0 ? (
                            <tr><td className="dt-val" colSpan="6">Aucun utilisateur</td></tr>
                        ) : users.map(function (user) {
                            return (
                                <tr key={user.id}>
                                    <td className="dt-val">{user.name || "-"}</td>
                                    <td className="dt-val">{user.email || "-"}</td>
                                    <td className="dt-val">{user.provider || "-"}</td>
                                    <td className="dt-val">{user.role || "reader"}</td>
                                    <td className="dt-val">{user.status || "active"}</td>
                                    <td className="dt-val">
                                        <div className="actions" style={{ margin: 0 }}>
                                            <button className="btn secondary" onClick={function () { updateUserRole(user.id, "reader"); }} disabled={busy}>reader</button>
                                            <button className="btn secondary" onClick={function () { updateUserRole(user.id, "editor"); }} disabled={busy}>editor</button>
                                            <button className="btn secondary" onClick={function () { updateUserRole(user.id, "admin"); }} disabled={busy}>admin</button>
                                            {user.status === "disabled" ? (
                                                <button className="btn secondary" onClick={function () { updateUserStatus(user.id, "active"); }} disabled={busy}>Activer</button>
                                            ) : (
                                                <button className="btn secondary" onClick={function () { updateUserStatus(user.id, "disabled"); }} disabled={busy}>Desactiver</button>
                                            )}
                                        </div>
                                    </td>
                                </tr>
                            );
                        })}
                    </tbody>
                </table>

                <div className={"status " + statusType} style={{ marginTop: "12px" }}>{status}</div>
            </section>
        </>
    );
}

function App() {
    const [authLoading, setAuthLoading] = useState(true);
    const [authError, setAuthError] = useState("");
    const [authUser, setAuthUser] = useState(null);
    const [authProviders, setAuthProviders] = useState({ google: false, facebook: false, dev: false });

    const role = authUser && authUser.role ? authUser.role : "reader";
    const canEdit = role === "editor" || role === "admin";
    const canAdmin = role === "admin";

    const menu = [
        { id: "udlSource", label: "Source Editor", group: "UDL", enabled: canEdit },
        { id: "udl", label: "Config Debug", group: "UDL", enabled: canEdit },
        { id: "udlRuntime", label: "Runtime Lab", group: "UDL", enabled: true },
        { id: "udlVersions", label: "Package", group: "UDL", enabled: canEdit },
        { id: "rulesEditor", label: "Rules Editor", group: "Outils", enabled: true },
        { id: "users", label: "Utilisateurs", group: "Admin", enabled: canAdmin },
        { id: "catalog", label: "Catalogue", group: "Outils", enabled: false },
        { id: "orders", label: "Commandes", group: "Outils", enabled: false }
    ];

    function resolveTab(rawValue) {
        var raw = String(rawValue || "").replace(/^#/, "").trim();
        var enabledIds = menu.filter(function (m) { return m.enabled; }).map(function (m) { return m.id; });
        return enabledIds.indexOf(raw) > -1 ? raw : (enabledIds[0] || "udlRuntime");
    }

    const [active, setActive] = useState(function () {
        return resolveTab(window.location.hash || "udlRuntime");
    });
    const [helpOpen, setHelpOpen] = useState(false);
    const [helpScreenId, setHelpScreenId] = useState(function () {
        return resolveTab(window.location.hash || "udlRuntime");
    });

    function refreshAuthState() {
        return Promise.all([
            fetch("/auth/config").then(function (res) { return parseApiResponseSafe(res, "/auth/config"); }),
            fetch("/auth/me").then(function (res) { return parseApiResponseSafe(res, "/auth/me"); })
        ]).then(function (payloads) {
            var cfg = payloads[0] && payloads[0].providers ? payloads[0].providers : { google: false, facebook: false, dev: false };
            var session = payloads[1] || {};
            setAuthProviders(cfg);
            setAuthUser(session.authenticated ? (session.user || null) : null);
            setAuthError("");
        });
    }

    function logout() {
        fetch("/auth/logout", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({})
        }).then(function (res) {
            return parseApiResponseSafe(res, "/auth/logout");
        }).then(function () {
            setAuthUser(null);
            setHelpOpen(false);
        }).catch(function (e) {
            setAuthError("Logout impossible: " + e.message);
        });
    }

    function loginDev(payload) {
        fetch("/auth/dev-login", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(payload || {})
        }).then(function (res) {
            return parseApiResponseSafe(res, "/auth/dev-login");
        }).then(function () {
            return refreshAuthState();
        }).catch(function (e) {
            setAuthError("Connexion dev impossible: " + e.message);
        });
    }

    useEffect(function () {
        refreshAuthState().catch(function (e) {
            setAuthError("Impossible de verifier la session: " + e.message);
        }).finally(function () {
            setAuthLoading(false);
        });
    }, []);

    useEffect(function () {
        function onHashChange() {
            var resolved = resolveTab(window.location.hash || "");
            if (resolved !== active) {
                setActive(resolved);
            }
        }

        window.addEventListener("hashchange", onHashChange);
        return function () {
            window.removeEventListener("hashchange", onHashChange);
        };
    }, [active]);

    useEffect(function () {
        var current = resolveTab(window.location.hash || "");
        if (current !== active) {
            window.location.hash = active;
        }
    }, [active, authUser]);

    useEffect(function () {
        var safe = resolveTab(active);
        if (safe !== active) {
            setActive(safe);
        }
    }, [authUser]);

    useEffect(function () {
        setHelpScreenId(active);
    }, [active]);

    useEffect(function () {
        function onKeyDown(event) {
            if (event.key === "Escape") {
                setHelpOpen(false);
            }
        }

        window.addEventListener("keydown", onKeyDown);
        return function () {
            window.removeEventListener("keydown", onKeyDown);
        };
    }, []);

    var enabledMenu = menu.filter(function (entry) { return entry.enabled; });
    var currentScreen = BACKOFFICE_HELP_SCREENS.find(function (screen) { return screen.id === active; }) || BACKOFFICE_HELP_SCREENS[0];
    var selectedHelpScreen = BACKOFFICE_HELP_SCREENS.find(function (screen) { return screen.id === helpScreenId; }) || currentScreen;

    if (authLoading) {
        return (
            <div className="app-shell" style={{ gridTemplateColumns: "1fr" }}>
                <main className="content">
                    <div className="status">Verification de session...</div>
                </main>
            </div>
        );
    }

    if (!authUser) {
        return <LoginScreen providers={authProviders} error={authError} onDevLogin={loginDev} />;
    }

    return (
        <div className="app-shell">
            <BackofficeSidebar menu={enabledMenu} active={active} onSelect={setActive} />

            <main className="content">
                <div className="content-topbar">
                    <div style={{ marginRight: "12px", color: "#4b5563", fontSize: "0.92rem" }}>
                        Connecte: <strong>{authUser.name || authUser.email || authUser.id}</strong> ({authUser.role})
                    </div>
                    <button className="btn secondary" onClick={logout}>Se deconnecter</button>
                    <BackofficeHelpButton onOpen={function () { setHelpScreenId(active); setHelpOpen(true); }} />
                </div>
                {active === "rulesEditor" ? <RulesEditor /> : null}
                {active === "udlSource" ? <UdlSourceEditor /> : null}
                {active === "udl" ? <UdlConfigDebug /> : null}
                {active === "udlVersions" ? <UdlPackage /> : null}
                {active === "udlRuntime" ? <UdlRuntimeLab /> : null}
                {active === "users" ? <UsersAdmin /> : null}
            </main>

            <BackofficeHelpModal
                open={helpOpen}
                onClose={function () { setHelpOpen(false); }}
                screens={BACKOFFICE_HELP_SCREENS}
                activeId={active}
                currentScreen={currentScreen}
                selectedScreen={selectedHelpScreen}
                onSelectScreen={setHelpScreenId}
            />
        </div>
    );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
