/**
 * Fichier  : assets/js/pages/users.js
 * Version  : 2.6.119
 * Date     : 2026-07-25
 * Description : JS — Utilisateurs : CRUD, rôles, permissions magasins + permissions fines
 *   v2.6.5 (2026-07-08)    — Harmonisation Lot 5 : data-label mobile, actions tactiles et cohérence visuelle admin.
 *   v2.6.70 (2026-07-11)   — I18N CORE LOCALE CURRENCY1 : dates utilisateur selon la locale active.
 *   v2.6.102 (2026-07-21) — Comptes LDAP identifiés, champs synchronisés verrouillés et suspension locale possible.
 *   v2.6.111 (2026-07-22) — Choix entre autorisations LDAP et surcharge locale MageStock.
 *   v2.6.113 (2026-07-22) — Rôle Magasinier et verrouillage des autorisations système sensibles.
 *   v2.6.119 (2026-07-25) — Création guidée des jetons de récupération locaux à usage unique.
 */
const UsrPage = {
    users: [],
    _esc(value){ return String(value ?? '').replace(/[&<>"']/g, c=>({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',"'":'&#39;'}[c])); },
    warehouses: [],
    roleLabel(role){ return role === 'admin' ? 'Admin' : (role === 'magasinier' ? 'Magasinier' : 'Utilisateur'); },
    roleBadge(role){ return role === 'admin' ? 'badge-blue' : (role === 'magasinier' ? 'badge-orange' : 'badge-green'); },
    ROLE_DEFAULTS: {
        user: {
            'products.read':true,'stock.read':true,'movements.read':true,'movements.create':true,'movements.delete':false,
            'inventory.read':true,'inventory.write':false,'analytics.read':true,'kits.read':true,'lots.read':true,
            'reservations.read':true,'valuation.read':false,'product.versions.read':true,'price.read':false,
            'locations.manage':false,'warehouses.manage':false,'plans.read':true,'plans.manage':false,
'settings.write':false,'users.manage':false,'auth.recovery.manage':false,'admin.access':false,
            'ldap.manage':false,'backup.manage':false,'archive.manage':false,'update.manage':false
        },
        magasinier: {
            'products.read':true,'stock.read':true,'movements.read':true,'movements.create':true,'movements.delete':true,
            'inventory.read':true,'inventory.write':true,'analytics.read':true,'kits.read':true,'lots.read':true,
            'reservations.read':true,'valuation.read':true,'product.versions.read':true,'price.read':true,
            'locations.manage':true,'warehouses.manage':true,'plans.read':true,'plans.manage':true,
'admin.access':true,'settings.write':false,'users.manage':false,'auth.recovery.manage':false,
            'ldap.manage':false,'backup.manage':false,'archive.manage':false,'update.manage':false
        }
    },

    // Toutes les permissions disponibles pour un utilisateur (role=user)
    PERM_DEFS: [
        { key: 'products.read',       label: 'Voir les produits',       group: 'Produits' },
        { key: 'stock.read',          label: 'Voir le stock',           group: 'Produits' },
        { key: 'movements.read',      label: 'Voir les mouvements',     group: 'Mouvements' },
        { key: 'movements.create',    label: 'Créer des mouvements',    group: 'Mouvements' },
        { key: 'movements.delete',    label: 'Supprimer des mouvements',group: 'Mouvements' },
        { key: 'inventory.read',      label: 'Voir les inventaires',    group: 'Inventaire' },
        { key: 'inventory.write',     label: 'Modifier les inventaires',group: 'Inventaire' },
        { key: 'kits.read',           label: 'Voir les kits',           group: 'Fonctions avancées' },
        { key: 'lots.read',           label: 'Voir les lots',           group: 'Fonctions avancées' },
        { key: 'reservations.read',   label: 'Voir les réservations',   group: 'Fonctions avancées' },
        { key: 'analytics.read',      label: 'Voir le pilotage',        group: 'Fonctions avancées' },
        { key: 'product.versions.read',label:'Historique produits',     group: 'Fonctions avancées' },
        { key: 'price.read',          label: 'Voir les prix',           group: 'Prix & Valorisation' },
        { key: 'valuation.read',      label: 'Voir la valorisation',    group: 'Prix & Valorisation' },
        { key: 'locations.manage',    label: 'Gérer les emplacements',  group: 'Administration' },
        { key: 'warehouses.manage',   label: 'Gérer les magasins',      group: 'Administration' },
        { key: 'settings.write',      label: 'Modifier les paramètres', group: 'Administration' },
        { key: 'users.manage',        label: 'Gérer les utilisateurs',  group: 'Administration' },
        { key: 'auth.recovery.manage',label: 'Créer des jetons de récupération', group: 'Système', sensitive: true },
        { key: 'admin.access',        label: 'Accès métier étendu',      group: 'Administration' },
        { key: 'ldap.manage',        label: 'Gérer LDAP',               group: 'Système', sensitive: true },
        { key: 'backup.manage',      label: 'Sauvegardes système',      group: 'Système', sensitive: true },
        { key: 'archive.manage',     label: 'Archives système',         group: 'Système', sensitive: true },
        { key: 'update.manage',      label: 'Mises à jour',             group: 'Système', sensitive: true },
    ],

    async init(){
        this.warehouses = await Api.warehouses();
        document.getElementById('userSearch').addEventListener('input', ()=>this.load());
        document.getElementById('btnNewUser').onclick = ()=>this.openModal();
        await this.load();
    },

    async load(){
        const q = document.getElementById('userSearch').value.toLowerCase();
        const users = await Api.users();
        this.users = users;
        const filtered = q ? users.filter(u=>
            (u.name||'').toLowerCase().includes(q)||(u.username||'').toLowerCase().includes(q)
        ) : users;
        document.getElementById('userCount').textContent = filtered.length + ' utilisateur(s)';
        this.renderTable(filtered);
    },

    renderTable(users){
        const tbody = document.getElementById('userBody');
        if(!users.length){
            tbody.innerHTML='<tr><td colspan="8" class="tbl-empty">Aucun utilisateur</td></tr>';
            return;
        }
        tbody.innerHTML = users.map(u=>{
            const d = u.createdAt ? Locale.formatDate(u.createdAt,{day:'2-digit',month:'short',year:'numeric'}) : '—';
            const isLdap = u.authSource === 'ldap';
            const ldapState = String(u.authStatus || u.ldapStatus || 'active');
            const authzLocal = isLdap && u.authorizationSource === 'local';
            const authBadge = isLdap
                ? `<span class="ldap-account-badge is-ldap ${['active','local'].includes(ldapState)?'':'is-error'}">LDAP${authzLocal?' + droits locaux':''}${ldapState !== 'active' ? ' · ' + ldapState : ''}</span>`
                : '<span class="ldap-account-badge is-local">Local</span>';
            let whDisplay = '—';
            if(u.role === 'admin'){
                whDisplay = '<span class="text-muted" style="font-size:11px">Tous (Admin)</span>';
            } else {
                const allowed = u.allowedWarehouses || [];
                if(allowed.length === 0){
                    whDisplay = '<span class="text-muted" style="font-size:11px">Tous</span>';
                } else {
                    const whNames = allowed.map(wid=>{
                        const wh = this.warehouses.find(w=>w.id===wid);
                        return wh ? wh.name : '?';
                    });
                    whDisplay = (whNames.length<=2)
                        ? whNames.map(n=>`<span class="badge badge-muted">${n}</span>`).join(' ')
                        : `<span class="badge badge-muted">${whNames[0]}</span> <span class="badge badge-muted">+${whNames.length-1}</span>`;
                }
            }
            return `<tr>
                <td data-label="Utilisateur"><strong>${u.name||'—'}</strong></td>
                <td data-label="Identifiant" class="col-mono">${u.username||'—'}</td>
                <td data-label="Authentification">${authBadge}</td>
                <td data-label="Rôle"><span class="badge ${this.roleBadge(u.role)}">${this.roleLabel(u.role)}</span></td>
                <td data-label="Magasins">${whDisplay}</td>
                <td data-label="Statut"><span class="badge ${u.active!==false?'badge-green':'badge-red'}">${u.active!==false?'Actif':'Inactif'}</span></td>
                <td data-label="Créé le">${d}</td>
                <td class="cell-actions" data-label="Actions" style="display:flex;gap:4px">
                    <button class="btn btn-sm btn-secondary" onclick="UsrPage.openModal('${u.id}')">Éditer</button>
                    ${u.recoveryEligible ? `<button class="btn btn-sm btn-secondary" title="Créer un jeton de récupération" onclick="UsrPage.openRecoveryModal('${u.id}')">Jeton</button>` : ''}
                    <button class="btn btn-sm btn-danger" onclick="UsrPage.delUser('${u.id}')">Suppr.</button>
                </td>
            </tr>`;
        }).join('');
    },

    async openModal(id){
        const u = id ? (this.users||[]).find(x=>String(x.id)===String(id))||{} : {};
        const isEdit = !!id;
        const isAdmin = (u.role||'user') === 'admin';
        const isMagasinier = (u.role||'user') === 'magasinier';
        const isLdap = u.authSource === 'ldap';
        const authzLocal = isLdap && u.authorizationSource === 'local';
        const allowedWarehouses = u.allowedWarehouses || [];

        // Charger les permissions existantes si édition
        let rolePerms = {};
        let userOverrides = {};
        if(isEdit && (!isAdmin || isLdap)){
            try {
                const pr = await Api.userPerms(id);
                rolePerms     = pr.rolePerms     || {};
                userOverrides = pr.userOverrides  || {};
            } catch(e){ /* silencieux */ }
        }

        // Warehouse checkboxes
        const whCheckboxes = this.warehouses.map(wh=>{
            const checked = allowedWarehouses.includes(wh.id) ? 'checked' : '';
            const disabled = isLdap && !authzLocal ? 'disabled' : '';
            return `<label style="display:flex;align-items:center;gap:8px;padding:6px 0;cursor:pointer">
                <input type="checkbox" class="wh-access-cb" value="${wh.id}" ${checked} ${disabled} style="width:16px;height:16px;flex-shrink:0">
                <span style="font-size:13px">${wh.name}</span>
            </label>`;
        }).join('');

        // Permission rows par groupe (seulement pour les users non-admin en édition)
        const permGroupsHtml = isEdit && (!isAdmin || isLdap) ? this._buildPermGroups(rolePerms, userOverrides) : '';

        Modal.show(isEdit?'Modifier utilisateur':'Nouvel utilisateur', `
            ${isLdap ? `<div class="ldap-security-banner"><strong>Authentification gérée par LDAP</strong><span>Le mot de passe et l’identité restent synchronisés depuis l’annuaire. Les autorisations peuvent rester synchronisées ou être surchargées dans MageStock.</span></div>` : ''}
            <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px">
                <div>
                    <label class="lbl">Nom complet *</label>
                    <input type="text" class="inp" id="umName" value="${u.name||''}" ${isLdap?'disabled':''}>
                </div>
                <div>
                    <label class="lbl">Username *</label>
                    <input type="text" class="inp" id="umUser" value="${u.username||''}" ${isEdit?'disabled':''}>
                </div>
            </div>

            ${isLdap ? `<div style="margin-top:12px"><label class="lbl">Mot de passe</label><div class="admin-info">Géré par l’annuaire — aucun mot de passe LDAP n’est stocké dans MageStock.</div><input type="hidden" id="umPass" value=""></div>` : `
            <div style="margin-top:12px">
                <label class="lbl">Mot de passe ${isEdit?'(laisser vide = inchangé)':'*'}</label>
                <input type="password" class="inp" id="umPass" placeholder="14 caractères, majuscule, minuscule, chiffre et symbole…" minlength="14" maxlength="255">
            </div>`}

            ${isLdap ? `<div class="ldap-authz-choice">
                <label class="lbl">Gestion des autorisations</label>
                <div class="ldap-authz-buttons">
                    <button type="button" class="btn btn-sm ${!authzLocal?'btn-primary':'btn-secondary'}" id="umAuthzDirectory" onclick="UsrPage._toggleAuthorization('directory')">Synchronisées depuis LDAP</button>
                    <button type="button" class="btn btn-sm ${authzLocal?'btn-primary':'btn-secondary'}" id="umAuthzLocal" onclick="UsrPage._toggleAuthorization('local')">Surcharge locale MageStock</button>
                </div>
                <p class="ldap-authz-help" id="umAuthzHelp">${authzLocal?'Le rôle, les permissions et les magasins sont définis localement. La synchronisation LDAP continue de mettre à jour l’identité et les groupes.':'Le rôle, les permissions et les magasins sont recalculés depuis les mappings LDAP à chaque synchronisation.'}</p>
            </div>` : ''}

            <div style="margin-top:16px">
                <label class="lbl">Rôle</label>
                <div style="display:flex;gap:8px;margin-top:6px">
                    <button type="button" class="btn btn-sm ${!isAdmin&&!isMagasinier?'btn-primary':'btn-secondary'}" id="umRoleUser" ${isLdap&&!authzLocal?'disabled':''} onclick="UsrPage._toggleRole('user')">👤 Utilisateur</button>
                    <button type="button" class="btn btn-sm ${isMagasinier?'btn-primary':'btn-secondary'}" id="umRoleMagasinier" ${isLdap&&!authzLocal?'disabled':''} onclick="UsrPage._toggleRole('magasinier')">📦 Magasinier</button>
                    <button type="button" class="btn btn-sm ${isAdmin?'btn-primary':'btn-secondary'}" id="umRoleAdmin" ${isLdap&&!authzLocal?'disabled':''} onclick="UsrPage._toggleRole('admin')">🛡 Admin</button>
                </div>
            </div>

            <div id="umUserSections" style="${isAdmin||(isLdap&&!authzLocal)?'display:none':''}">
                <div style="margin-top:16px">
                    <label class="lbl">Accès aux magasins</label>
                    <p style="font-size:11px;color:var(--text-muted);margin-bottom:8px">
                        Si aucun magasin sélectionné, l'utilisateur aura accès à tous.
                    </p>
                    <div style="max-height:120px;overflow-y:auto;border:1px solid var(--border);border-radius:8px;padding:6px 12px;background:var(--bg-input)">
                        ${whCheckboxes || '<p style="color:var(--text-muted);font-size:12px">Aucun magasin disponible</p>'}
                    </div>
                </div>

                ${permGroupsHtml ? `
                <div style="margin-top:16px">
                    <label class="lbl">Permissions fines</label>
                    <p style="font-size:11px;color:var(--text-muted);margin-bottom:10px">
                        Cochez pour activer. Les cases grisées correspondent aux valeurs par défaut du rôle.
                    </p>
                    <div id="umPermsContainer" style="border:1px solid var(--border);border-radius:8px;background:var(--bg-input);overflow:hidden">
                        ${permGroupsHtml}
                    </div>
                </div>
                ` : ''}
            </div>

            ${isEdit?`
            <div style="margin-top:16px">
                <label class="lbl">Statut</label>
                <div style="display:flex;gap:8px;margin-top:6px">
                    <button type="button" class="btn btn-sm ${u.active!==false?'btn-primary':'btn-secondary'}" id="umActif" onclick="UsrPage._toggleStatus(true)">✓ Actif</button>
                    <button type="button" class="btn btn-sm ${u.active===false?'btn-primary':'btn-secondary'}" id="umInactif" onclick="UsrPage._toggleStatus(false)">✗ Inactif</button>
                </div>
            </div>
            `:''}
        `, [
            {label:'Annuler', cls:'btn-secondary', onclick:()=>Modal.close()},
            {label:isEdit?'Sauvegarder':'Créer', cls:'btn-primary', onclick:()=>UsrPage.saveUser(id)},
            ...(isEdit?[{label:'Supprimer', cls:'btn-danger', onclick:()=>UsrPage.delUser(id)}]:[])
        ], isEdit?'info':'success');
        this._syncRolePermissionLocks(isAdmin ? 'admin' : (isMagasinier ? 'magasinier' : 'user'), isLdap && !authzLocal);
    },

    openRecoveryModal(id){
        const user = (this.users || []).find(item => String(item.id) === String(id));
        if(!user){ toast('Utilisateur introuvable','error'); return; }
        if(user.authSource === 'ldap'){
            toast('Le mot de passe de ce compte est géré par l’annuaire LDAP.','error');
            return;
        }
        if(user.active === false){
            toast('Le compte doit être actif avant de créer un jeton.','error');
            return;
        }
        Modal.show('Créer un jeton de récupération', `
            <div class="ldap-security-banner">
                <strong>Secret temporaire affiché une seule fois</strong>
                <span>Le jeton remplace tout jeton actif précédent. Transmettez-le par un canal sûr et ne le copiez jamais dans un ticket ou un journal.</span>
            </div>
            <div style="margin-top:14px">
                <label class="lbl">Compte local</label>
                <div class="admin-info"><strong>${this._esc(user.name || user.username)}</strong><br>${this._esc(user.username || '')}</div>
            </div>
            <div style="margin-top:14px">
                <label class="lbl" for="recoveryDuration">Durée de validité</label>
                <select class="inp" id="recoveryDuration">
                    <option value="15">15 minutes</option>
                    <option value="30">30 minutes</option>
                    <option value="60" selected>1 heure</option>
                    <option value="120">2 heures</option>
                    <option value="240">4 heures</option>
                </select>
                <p style="margin:7px 0 0;color:var(--text-muted);font-size:12px">Une nouvelle création invalide immédiatement le jeton précédent.</p>
            </div>
        `, [
            {label:'Annuler', cls:'btn-secondary', onclick:()=>Modal.close()},
            {label:'Créer le jeton', cls:'btn-primary', onclick:()=>UsrPage.generateRecoveryToken(id)}
        ], 'info');
    },

    async generateRecoveryToken(id){
        const duration = Number(document.getElementById('recoveryDuration')?.value || 60);
        try {
            const result = await Api.userRecoveryToken(id, duration);
            const expiry = result.expiresAt && window.Locale
                ? Locale.formatDate(result.expiresAt, {dateStyle:'medium', timeStyle:'short'})
                : (result.expiresAt || '—');
            const recoveryUrl = new URL(Api.url('password-recovery'), window.location.href).href;
            Modal.show('Jeton prêt à transmettre', `
                <div class="ldap-security-banner">
                    <strong>Copiez maintenant le jeton</strong>
                    <span>Après fermeture de cette fenêtre, MageStock ne pourra plus afficher ce secret. Seul son SHA-256 est conservé.</span>
                </div>
                <div style="margin-top:14px">
                    <label class="lbl" for="recoveryTokenValue">Jeton à usage unique</label>
                    <textarea class="inp" id="recoveryTokenValue" readonly rows="3" spellcheck="false" style="font-family:var(--font-mono,monospace);resize:none">${this._esc(result.token || '')}</textarea>
                    <button type="button" class="btn btn-secondary btn-sm" style="margin-top:8px" onclick="UsrPage.copyRecoveryValue('recoveryTokenValue','Jeton copié')">Copier le jeton</button>
                </div>
                <div style="margin-top:14px">
                    <label class="lbl" for="recoveryPageValue">Page de récupération</label>
                    <input class="inp" id="recoveryPageValue" readonly value="${this._esc(recoveryUrl)}">
                    <button type="button" class="btn btn-secondary btn-sm" style="margin-top:8px" onclick="UsrPage.copyRecoveryValue('recoveryPageValue','Adresse copiée')">Copier l’adresse</button>
                </div>
                <div class="admin-info" style="margin-top:14px"><strong>Expiration :</strong> ${this._esc(expiry)}<br>Le jeton ne doit jamais être ajouté à l’adresse et ne peut servir qu’une seule fois.</div>
            `, [
                {label:'Fermer', cls:'btn-primary', onclick:()=>Modal.close()}
            ], 'success');
        } catch(error){
            toast(error.message || 'Impossible de créer le jeton','error');
        }
    },

    async copyRecoveryValue(id, successLabel){
        const field = document.getElementById(id);
        if(!field) return;
        const value = field.value || field.textContent || '';
        try {
            await navigator.clipboard.writeText(value);
        } catch(_) {
            field.focus();
            field.select?.();
            document.execCommand?.('copy');
        }
        toast(successLabel || 'Copié','success');
    },

    _buildPermGroups(rolePerms, userOverrides){
        // Regrouper les permissions par groupe
        const groups = {};
        this.PERM_DEFS.forEach(p=>{
            if(!groups[p.group]) groups[p.group] = [];
            groups[p.group].push(p);
        });
        let html = '';
        let first = true;
        for(const [grpName, perms] of Object.entries(groups)){
            if(!first) html += '<div style="border-top:1px solid var(--border)"></div>';
            first = false;
            html += `<div style="padding:10px 14px">
                <div style="font-size:10px;font-weight:700;letter-spacing:0.08em;text-transform:uppercase;color:var(--text-muted);margin-bottom:8px">${grpName}</div>
                <div style="display:grid;grid-template-columns:1fr 1fr;gap:4px">`;
            perms.forEach(p=>{
                // valeur effective : override prioritaire, sinon role
                const effective = userOverrides.hasOwnProperty(p.key) ? userOverrides[p.key] : (rolePerms[p.key] ?? false);
                const hasOverride = userOverrides.hasOwnProperty(p.key);
                const checked = effective ? 'checked' : '';
                const sensitive = p.sensitive ? 'data-sensitive-permission="1"' : '';
                const dimStyle = !hasOverride ? 'opacity:0.6' : '';
                html += `<label style="display:flex;align-items:center;gap:6px;padding:4px 0;cursor:pointer;${dimStyle}">
                    <input type="checkbox" class="perm-cb" data-perm="${p.key}" ${sensitive} ${checked} style="width:14px;height:14px;flex-shrink:0">
                    <span style="font-size:12px;color:var(--text-primary)">${p.label}</span>
                </label>`;
            });
            html += `</div></div>`;
        }
        return html;
    },

    _toggleAuthorization(source){
        const directory = source !== 'local';
        const btnDirectory = document.getElementById('umAuthzDirectory');
        const btnLocal = document.getElementById('umAuthzLocal');
        const btnUser = document.getElementById('umRoleUser');
        const btnMagasinier = document.getElementById('umRoleMagasinier');
        const btnAdmin = document.getElementById('umRoleAdmin');
        const help = document.getElementById('umAuthzHelp');
        [btnDirectory, btnLocal].forEach(btn=>btn && btn.classList.remove('btn-primary'));
        [btnDirectory, btnLocal].forEach(btn=>btn && btn.classList.add('btn-secondary'));
        const activeBtn = directory ? btnDirectory : btnLocal;
        activeBtn?.classList.add('btn-primary'); activeBtn?.classList.remove('btn-secondary');
        [btnUser, btnMagasinier, btnAdmin].forEach(btn=>{ if(btn) btn.disabled = directory; });
        document.querySelectorAll('.wh-access-cb,.perm-cb').forEach(el=>{ el.disabled = directory; });
        const roleAdmin = btnAdmin?.classList.contains('btn-primary');
        const sections = document.getElementById('umUserSections');
        if(sections) sections.style.display = (!directory && !roleAdmin) ? '' : 'none';
        this._syncRolePermissionLocks(roleAdmin ? 'admin' : (btnMagasinier?.classList.contains('btn-primary') ? 'magasinier' : 'user'), directory);
        if(help) help.textContent = directory
            ? 'Le rôle, les permissions et les magasins seront restaurés depuis le dernier instantané LDAP et recalculés aux prochaines synchronisations.'
            : 'Le rôle, les permissions et les magasins sont définis localement. La synchronisation LDAP continue de mettre à jour l’identité et les groupes.';
    },

    _toggleRole(role){
        const btnUser   = document.getElementById('umRoleUser');
        const btnMagasinier = document.getElementById('umRoleMagasinier');
        const btnAdmin  = document.getElementById('umRoleAdmin');
        const sections  = document.getElementById('umUserSections');
        if(role === 'admin'){
            btnAdmin.classList.add('btn-primary');    btnAdmin.classList.remove('btn-secondary');
            btnUser.classList.remove('btn-primary');  btnUser.classList.add('btn-secondary');
            btnMagasinier.classList.remove('btn-primary'); btnMagasinier.classList.add('btn-secondary');
            sections.style.display = 'none';
        } else if(role === 'magasinier'){
            btnMagasinier.classList.add('btn-primary'); btnMagasinier.classList.remove('btn-secondary');
            btnUser.classList.remove('btn-primary'); btnUser.classList.add('btn-secondary');
            btnAdmin.classList.remove('btn-primary'); btnAdmin.classList.add('btn-secondary');
            sections.style.display = '';
        } else {
            btnUser.classList.add('btn-primary');     btnUser.classList.remove('btn-secondary');
            btnMagasinier.classList.remove('btn-primary'); btnMagasinier.classList.add('btn-secondary');
            btnAdmin.classList.remove('btn-primary'); btnAdmin.classList.add('btn-secondary');
            sections.style.display = '';
        }
        this._applyRoleDefaults(role);
        this._syncRolePermissionLocks(role);
    },

    _applyRoleDefaults(role){
        const defaults = this.ROLE_DEFAULTS[role];
        if(!defaults) return;
        document.querySelectorAll('.perm-cb').forEach(cb=>{
            if(Object.prototype.hasOwnProperty.call(defaults, cb.dataset.perm)) cb.checked = !!defaults[cb.dataset.perm];
        });
    },

    _syncRolePermissionLocks(role, forceDisabled=false){
        document.querySelectorAll('.perm-cb[data-sensitive-permission="1"]').forEach(cb=>{
            const locked = role === 'magasinier';
            cb.disabled = forceDisabled || locked;
            if(locked) cb.checked = false;
            cb.closest('label')?.classList.toggle('is-role-locked', locked);
        });
    },

    _toggleStatus(active){
        const btnActif  = document.getElementById('umActif');
        const btnInactif= document.getElementById('umInactif');
        if(active){
            btnActif.classList.add('btn-primary');   btnActif.classList.remove('btn-secondary');
            btnInactif.classList.remove('btn-primary'); btnInactif.classList.add('btn-secondary');
        } else {
            btnInactif.classList.add('btn-primary'); btnInactif.classList.remove('btn-secondary');
            btnActif.classList.remove('btn-primary');    btnActif.classList.add('btn-secondary');
        }
    },

    async saveUser(id){
        const existing = id ? (this.users||[]).find(u=>String(u.id)===String(id)) : null;
        const isLdap = existing?.authSource === 'ldap';
        if(isLdap){
            const active = document.getElementById('umActif')?.classList.contains('btn-primary') ?? true;
            const authorizationSource = document.getElementById('umAuthzLocal')?.classList.contains('btn-primary') ? 'local' : 'directory';
            const body = {id:parseInt(id), active, authorizationSource};
            let role = existing.role || 'user';
            if(authorizationSource === 'local'){
                role = document.getElementById('umRoleAdmin').classList.contains('btn-primary') ? 'admin' : (document.getElementById('umRoleMagasinier').classList.contains('btn-primary') ? 'magasinier' : 'user');
                body.role = role;
                body.allowedWarehouses = Array.from(document.querySelectorAll('.wh-access-cb:checked')).map(cb=>parseInt(cb.value));
            }
            try {
                await Api.userEdit(body);
                if(authorizationSource === 'local' && role !== 'admin'){
                    const permissions = {};
                    document.querySelectorAll('.perm-cb').forEach(cb=>{ permissions[cb.dataset.perm] = cb.checked; });
                    await Api.userPermsEdit({userId:String(id), permissions});
                }
                toast(authorizationSource === 'local' ? 'Autorisations locales enregistrées' : 'Autorisations LDAP restaurées','success');
                Modal.close();
                await this.load();
            } catch(e){ toast(e.message || 'Erreur','error'); }
            return;
        }
        const name = document.getElementById('umName').value.trim();
        if(!name){ toast('Nom obligatoire','error'); return; }

        const role = document.getElementById('umRoleAdmin').classList.contains('btn-primary') ? 'admin' : (document.getElementById('umRoleMagasinier').classList.contains('btn-primary') ? 'magasinier' : 'user');
        const pass = document.getElementById('umPass').value;

        if(!id && !pass){ toast('Mot de passe obligatoire pour un nouvel utilisateur','error'); return; }
        if(pass && [...pass].length < 14){ toast('Le mot de passe doit contenir au moins 14 caractères.','error'); return; }

        const whCbs = document.querySelectorAll('.wh-access-cb:checked');
        const allowedWarehouses = Array.from(whCbs).map(cb=>parseInt(cb.value));

        const body = { name, role, allowedWarehouses };
        if(!id) body.username = document.getElementById('umUser').value.trim();
        if(pass) body.password = pass;
        if(id){
            body.id = parseInt(id);
            const actBtn = document.getElementById('umActif');
            if(actBtn) body.active = actBtn.classList.contains('btn-primary');
        }

        try {
            let savedUser;
            if(id) savedUser = await Api.userEdit(body);
            else   savedUser = await Api.userAdd(body);

            // Sauvegarder permissions fines si présentes
            if(role !== 'admin'){
                const permCbs = document.querySelectorAll('.perm-cb');
                if(permCbs.length > 0){
                    const permissions = {};
                    permCbs.forEach(cb=>{ permissions[cb.dataset.perm] = cb.checked; });
                    const userId = id || (savedUser?.id);
                    if(userId) await Api.userPermsEdit({ userId: String(userId), permissions });
                }
            }

            toast(id?'Utilisateur mis à jour':'Utilisateur créé', 'success');
            Modal.close();
            this.load();
        } catch(e){
            toast(e.message || 'Erreur', 'error');
        }
    },

    async delUser(id){
        if(!confirm('Supprimer cet utilisateur ?')) return;
        try {
            await Api.userDel(id);
            toast('Utilisateur supprimé','success');
            Modal.close();
            this.load();
        } catch(e){
            toast(e.message||'Erreur','error');
        }
    }
};
