/**
 * Fichier  : assets/js/pages/products.js
 * Auteur   : KREMER Régis
 * Version  : 2.2.0
 * Date     : 2026-02-21
 * Description : JS — Catalogue produits : CRUD, images, packaging, coûts
 *
 * Changelog :
 *   v2.1.0 (2026-02-21) — Version initiale auditée et sécurisée
 *   v2.1.1 (2026-02-21) — Corrections sécurité, utils.js centralisé
 *   v2.2.0 (2026-02-21) — Permissions magasins, bons de commande, rapport d'écart,
 *                      import CSV, historique prix, bon de livraison multi-lignes
 */
// ================================================================
// products.js — Product grid with brand, cost, supplier, datasheets
// ================================================================
const ProdPage = {
    _units: [
        {key:'piece',  label:'pièce'},
        {key:'carton', label:'carton'},
        {key:'palette',label:'palette'},
        {key:'bidon',  label:'bidon'},
        {key:'fut',    label:'fût'},
        {key:'aerosol',label:'aérosol'},
        {key:'sac',    label:'sac'},
        {key:'seau',   label:'seau'},
        {key:'rouleau',label:'rouleau'},
        {key:'bobine', label:'bobine'},
        {key:'tube',   label:'tube'},
        {key:'boite',  label:'boîte'},
        {key:'paquet', label:'paquet'},
        // IMPORTANT : ici "ml" = mètre linéaire (et non millilitre)
        {key:'ml',     label:'mètre linéaire (ml)'},
        {key:'m',      label:'m'},
        {key:'m2',     label:'m²'},
        {key:'m3',     label:'m³'},
        {key:'litre',  label:'litre'},
        {key:'kg',     label:'kg'},
    ],

    // Pagination state
    pageIndex: 1,
    pageSize: 12,
    _shown: [],
    _currentView: 'grid',
    _currentWhFilter: '',

    _getBaseUnit(p){
        const u = (p?.packBaseUnit || '').trim();
        if(u) return u;
        const legacy = (p?.unitType || 'piece');
        if(legacy === 'liter') return 'litre';
        return legacy || 'piece';
    },
    
    _buildUnitOptions(p){
        const base = this._getBaseUnit(p);
        const hasCarton = (parseFloat(p?.packCartonQty)||0) > 0;
        const hasPalette= (parseFloat(p?.packPaletteQty)||0) > 0;
        const hasBidon  = (parseFloat(p?.packBidonQty)||0) > 0;
        const hasFut    = (parseFloat(p?.packFutQty)||0) > 0;
        const hasAerosol= (parseFloat(p?.packAerosolQty)||0) > 0;
        const hasSac    = (parseFloat(p?.packSacQty)||0) > 0;
        const hasSeau   = (parseFloat(p?.packSeauQty)||0) > 0;
        const hasRouleau= (parseFloat(p?.packRouleauQty)||0) > 0;
        const hasBobine = (parseFloat(p?.packBobineQty)||0) > 0;
        const hasTube   = (parseFloat(p?.packTubeQty)||0) > 0;
        const hasBoite  = (parseFloat(p?.packBoiteQty)||0) > 0;
        const hasPaquet = (parseFloat(p?.packPaquetQty)||0) > 0;

        const allowed = new Set();
        allowed.add(base);
        if(base === 'piece') allowed.add('piece');
        if(hasCarton) allowed.add('carton');
        if(hasPalette) allowed.add('palette');
        if(hasBidon) allowed.add('bidon');
        if(hasFut) allowed.add('fut');
        if(hasAerosol) allowed.add('aerosol');
        if(hasSac) allowed.add('sac');
        if(hasSeau) allowed.add('seau');
        if(hasRouleau) allowed.add('rouleau');
        if(hasBobine) allowed.add('bobine');
        if(hasTube) allowed.add('tube');
        if(hasBoite) allowed.add('boite');
        if(hasPaquet) allowed.add('paquet');

        return this._units
            .filter(u=>allowed.has(u.key))
            .map(u=>`<option value="${u.key}">${u.label}</option>`)
            .join('');
    },
    _getUnitCostPrefill(p, unit){
        const map = (p && typeof p.costByUnit === 'object' && p.costByUnit) ? p.costByUnit : {};
        const v = map?.[unit];
        if(v!=null && v!=='' && !Number.isNaN(parseFloat(v))) return parseFloat(v);
        const base = this._getBaseUnit(p);
        if(unit === base && !Number.isNaN(parseFloat(p?.cump))) return parseFloat(p.cump);
        if(unit === base && !Number.isNaN(parseFloat(p?.cost))) return parseFloat(p.cost);
        if(!Number.isNaN(parseFloat(p?.cost))) return parseFloat(p.cost);
        return 0;
    },
    async init(){
        const [cats, whs, suppliers] = await Promise.all([Api.categories(), Api.warehouses(), Api.suppliers()]);
        this.cats = cats || [];
        this.whs = whs || [];
        this.suppliers = suppliers || [];

        // Fill category filter
        const catSel = document.getElementById('prodCatFilter');
        if(catSel){
            const allCategories = (window.StockCascade ? StockCascade.getWarehouseCategories(this.whs, '', this._allProds || [], this.cats || []) : (this.cats || []));
            catSel.innerHTML = '<option value="">Toutes catégories</option>' + allCategories.map(c=>`<option value="${c}">${c}</option>`).join('');
        }

        // Fill warehouse filter (visible)
        const whSel = document.getElementById('prodWhFilter');
        if(whSel){
            whSel.innerHTML = '<option value="">Tous magasins</option>' + this.whs.map(w=>`<option value="${w.id}">${w.name}</option>`).join('');
            whSel.onchange = ()=> this.load();
        }

        // Events
        const searchEl = document.getElementById('prodSearch');
        if(searchEl) searchEl.oninput = ()=> this.load();
        
        const catEl = document.getElementById('prodCatFilter');
        if(catEl) catEl.onchange = ()=> this.load();

        const brandEl = document.getElementById('prodBrandFilter');
        if(brandEl) brandEl.onchange = ()=> this.load();
        
        const btn = document.getElementById('btnNewProd');
        if(btn) btn.onclick = ()=> this.openModal();

        // CSV Import
        const btnImport = document.getElementById('btnImportCSV');
        if(btnImport) btnImport.onclick = () => this.openImportModal();

        const btnEntry = document.getElementById('btnStockEntry');
        // Use global movement modal instead of local one
        if(btnEntry) btnEntry.onclick = ()=> window.openMovementModal('entry');

        // View toggle (grid/list)
        this._initViewToggle();
        this._initBulkMode();

        // Pager events
        const prevBtn = document.getElementById('prodPrev');
        const nextBtn = document.getElementById('prodNext');
        const sizeSel = document.getElementById('prodPageSize');
        if(sizeSel){
            const v = parseInt(sizeSel.value||'12',10);
            if(v>0) this.pageSize = v;
            sizeSel.onchange = ()=>{ this.pageSize = parseInt(sizeSel.value||'12',10) || 12; this.pageIndex = 1; this.renderPaged(); };
        }
        if(prevBtn) prevBtn.onclick = ()=>{ if(this.pageIndex>1){ this.pageIndex--; this.renderPaged(); } };
        if(nextBtn) nextBtn.onclick = ()=>{ const max=this._getMaxPage(); if(this.pageIndex<max){ this.pageIndex++; this.renderPaged(); } };

        // ══════════════════════════════════════════════════════════════
        // INFINITE SCROLL - Init pour mobile/tablette
        // ══════════════════════════════════════════════════════════════
        this._initInfiniteScroll();
        this._initImageZoom();

        await this.load();

        // ── Tri des colonnes ─────────────────────────────────────
        if(typeof TableSort !== 'undefined'){
            TableSort.init('prodTable',
                () => this._shown || [],
                (sorted) => {
                    this._shown = sorted;
                    this.pageIndex = 1;
                    this.renderPaged();
                },
                {
                    defaultKey: 'name',
                    defaultDir: 'asc',
                    getters: {
                        __stock: p => {
                            const entries = (this.stock||[]).filter(s=>String(s.productId)===String(p.id));
                            return entries.reduce((sum,s)=>sum+(parseInt(s.quantity)||0), 0);
                        },
                        __price: p => getBaseCost(p),
                        __wh: p => {
                            let aw = p.allowedWarehouses;
                            if(typeof aw==='string') try{aw=JSON.parse(aw||'[]')}catch(e){aw=[];}
                            return Array.isArray(aw)&&aw.length
                                ? (this.whs.find(w=>String(w.id)===String(aw[0]))||{}).name||''
                                : '';
                        }
                    }
                }
            );
        }
    },
    
    /**
     * Initialise le scroll infini pour mobile
     */
    _initInfiniteScroll(){
        if(typeof InfiniteScroll === 'undefined') return;
        
        const grid = document.getElementById('prodGrid');
        const pager = document.querySelector('#pgProducts .pager, #pgProducts .pagination-bar');
        
        if(!grid) return;
        
        // Ajouter classe pour cacher le pager sur mobile
        if(pager) pager.classList.add('hide-mobile');
        
        InfiniteScroll.init({
            id: 'products',
            container: document.querySelector('.main-content') || window,
            target: grid,
            pagerEl: pager,
            pageSize: this.pageSize || 12,
            threshold: 300,
            loadMore: async (page, size) => {
                // Calculer les items à retourner
                const start = (page - 1) * size;
                const end = start + size;
                const items = (this._shown || []).slice(start, end);
                
                return {
                    items: items,
                    hasMore: end < (this._shown || []).length
                };
            },
            renderItems: (items, append) => {
                this._renderGridItems(items, this._currentWhFilter || '', append);
            }
        });
    },
    
    // View toggle: grid vs list
    _initViewToggle(){
        const btnGrid = document.getElementById('viewGrid');
        const btnList = document.getElementById('viewList');
        const grid = document.getElementById('prodGrid');
        const tableWrap = document.getElementById('prodTableWrap');
        
        // Restore from localStorage
        const savedView = localStorage.getItem('sm_products_view') || 'grid';
        this._currentView = savedView;
        this._applyView(savedView);
        
        if(btnGrid){
            btnGrid.onclick = () => {
                this._currentView = 'grid';
                localStorage.setItem('sm_products_view', 'grid');
                this._applyView('grid');
            };
        }
        if(btnList){
            btnList.onclick = () => {
                this._currentView = 'list';
                localStorage.setItem('sm_products_view', 'list');
                this._applyView('list');
            };
        }
    },
    
    _applyView(view){
        const btnGrid = document.getElementById('viewGrid');
        const btnList = document.getElementById('viewList');
        const grid = document.getElementById('prodGrid');
        const tableWrap = document.getElementById('prodTableWrap');

        if(btnGrid) btnGrid.classList.toggle('active', view === 'grid');
        if(btnList) btnList.classList.toggle('active', view === 'list');

        if(view === 'list'){
            if(grid) grid.style.display = 'none';
            if(tableWrap) tableWrap.style.display = 'block';
            this.renderPaged();
        } else {
            if(grid) grid.style.display = '';
            if(tableWrap) tableWrap.style.display = 'none';
            this.renderPaged();
        }
        // Sync visibilité du bouton Sélection (visible seulement en vue liste)
        this._syncBulkToggleVisibility();
    },

    // Create an initial stock movement if the user provided an initial quantity at creation.
    // SAFE: no-op when qty is empty/0 or productId missing.
    async _maybeInitStock(productId, initQty, baseUnit, initLot, initExp){
        const pid = parseInt(productId || 0);
        const qty = parseFloat(initQty || 0);
        if(!pid || !qty || qty <= 0) return;

        // Warehouse: use selected warehouse from the form
        const whSelect = document.getElementById('pmWarehouse');
        const wid = parseInt(whSelect?.value) || 0;
        
        if(!wid){
            toast('Stock initial ignoré : aucun magasin sélectionné.', 'warning');
            return;
        }

        // Enforce lot/expiry if required (the backend will also enforce).
        const needsLot = !!document.getElementById('pmTrackLot')?.checked;
        const needsExp = !!document.getElementById('pmTrackExp')?.checked;
        const lot = (initLot || '').trim();
        const exp = (initExp || '').trim();
        if(needsLot && !lot){
            toast('Stock initial ignoré : numéro de lot obligatoire.', 'warning');
            return;
        }
        if(needsExp && !exp){
            toast('Stock initial ignoré : DLUO / péremption obligatoire.', 'warning');
            return;
        }

        // Unit cost: use current base cost as purchase cost when initializing.
        const unitCost = parseFloat(document.getElementById('pmCost')?.value || 0) || 0;
        await Api.mvAdd({
            type: 'add',
            productId: pid,
            warehouseId: wid,
            quantity: qty,
            unit: baseUnit || 'piece',
            lotNumber: lot || '',
            expiryDate: exp || '',
            unitCost,
            reason: 'Stock initial',
            site: '',
            operator: '',
            note: ''
        });
    },
    async load(){
        const q     = document.getElementById('prodSearch').value;
        const cat   = document.getElementById('prodCatFilter').value;
        const wh    = document.getElementById('prodWhFilter') ? document.getElementById('prodWhFilter').value : '';

        // Location labels (for UX: show emplacement in table/cards). Only meaningful when a warehouse is selected.
        this._locationsById = {};
        if (wh) {
            try {
                const locs = await Api.locations(wh);
                (locs || []).forEach(l => { this._locationsById[String(l.id)] = l; });
            } catch (e) {
                this._locationsById = {};
            }
        }
        const brand = document.getElementById('prodBrandFilter') ? document.getElementById('prodBrandFilter').value : '';
        try {
            // Charger tous les produits (sans filtre catégorie) pour pouvoir construire la liste des catégories
            const [allProds, stock] = await Promise.all([Api.products(q), Api.stock()]);
            this.stock = stock;
            this._allProds = allProds; // Référence complète pour les cascades bulk
            let shown = allProds;
            
            // Filtrer par magasin sélectionné
            if(wh){
                const whStr = String(wh);
                shown = window.StockCascade
                    ? StockCascade.getProductsForWarehouse(allProds, wh, false)
                    : allProds.filter(p=>{
                        let allowed = p.allowedWarehouses;
                        if(typeof allowed === 'string') {
                            try { allowed = JSON.parse(allowed || '[]'); } catch(e) { allowed = []; }
                        }
                        if(!Array.isArray(allowed) || !allowed.length) return false;
                        return allowed.some(a => String(a) === whStr);
                    });

                this._updateCategoryFilter(wh, allProds);
                this._updateBrandFilter(shown);
            } else {
                this._updateCategoryFilter(null, allProds);
                this._updateBrandFilter(allProds);
            }
            
            // Appliquer le filtre catégorie APRÈS avoir mis à jour la liste des catégories
            if(cat){
                shown = shown.filter(p => p.category === cat);
            }

            // Appliquer le filtre marque
            if(brand){
                shown = shown.filter(p => (p.brand || '') === brand);
            }
            
            document.getElementById('prodCount').textContent = shown.length+' produit(s)';
            this._shown = shown;
            this.pageIndex = 1;
            this._currentWhFilter = wh || '';
            // Appliquer le tri actif si existant
            this._shown = TableSort.sortData('prodTable', this._shown);
            this.renderPaged();
        } catch(e){ toast(e.message,'error'); }
    },
    
    _updateCategoryFilter(warehouseId, products){
        const catSel = document.getElementById('prodCatFilter');
        if(!catSel) return;
        
        const currentValue = catSel.value;
        
        const categories = window.StockCascade
            ? StockCascade.getWarehouseCategories(this.whs || [], warehouseId || '', products || [], this.cats || [])
            : [...new Set((products||[]).map(p => p.category).filter(Boolean))].sort();

        catSel.innerHTML = '<option value="">Toutes catégories</option>' + 
            categories.map(c => `<option value="${c}"${c === currentValue ? ' selected' : ''}>${c}</option>`).join('');
    },
    
    _updateBrandFilter(products){
        const brandSel = document.getElementById('prodBrandFilter');
        if(!brandSel) return;
        const currentValue = brandSel.value;
        const brands = [...new Set(products.map(p => p.brand).filter(Boolean))].sort();
        brandSel.innerHTML = '<option value="">Toutes marques</option>' +
            brands.map(b => `<option value="${b}"${b === currentValue ? ' selected' : ''}>${b}</option>`).join('');
    },

    _getMaxPage(){
        const total = (this._shown||[]).length;
        return Math.max(1, Math.ceil(total / (this.pageSize || 12)));
    },

    renderPaged(){
        const total = (this._shown||[]).length;
        const maxPage = this._getMaxPage();
        if(this.pageIndex > maxPage) this.pageIndex = maxPage;
        
        // ══════════════════════════════════════════════════════════════
        // MODE SCROLL INFINI (mobile/tablette)
        // ══════════════════════════════════════════════════════════════
        if(typeof InfiniteScroll !== 'undefined' && InfiniteScroll.isMobileMode()){
            // Reset et afficher la première page
            const firstPage = (this._shown||[]).slice(0, this.pageSize || 12);
            
            // Reset l'instance
            InfiniteScroll.reset('products', firstPage);
            InfiniteScroll.setTotal('products', total);
            
            // Rendre les items (mode replace, pas append)
            if(this._currentView === 'list'){
                this.renderTable(firstPage, this._currentWhFilter || '');
            } else {
                this._renderGridItems(firstPage, this._currentWhFilter || '', false);
            }
            return;
        }
        
        // ══════════════════════════════════════════════════════════════
        // MODE PAGINATION CLASSIQUE (desktop)
        // ══════════════════════════════════════════════════════════════
        const start = (this.pageIndex - 1) * (this.pageSize || 12);
        const end = start + (this.pageSize || 12);
        const slice = (this._shown||[]).slice(start, end);

        // Update pager UI
        const info = document.getElementById('prodPageInfo');
        if(info) info.textContent = `Page ${this.pageIndex} / ${maxPage} · ${total} résultat(s)`;
        const prevBtn = document.getElementById('prodPrev');
        const nextBtn = document.getElementById('prodNext');
        if(prevBtn) prevBtn.disabled = (this.pageIndex <= 1);
        if(nextBtn) nextBtn.disabled = (this.pageIndex >= maxPage);

        // Render based on current view
        if(this._currentView === 'list'){
            this.renderTable(slice, this._currentWhFilter || '');
        } else {
            this.renderGrid(slice, this._currentWhFilter || '');
        }
    },
    
    renderTable(prods, whFilter){
        const tbody = document.getElementById('prodTableBody');
        if(!tbody) return;

        if(!prods.length){
            tbody.innerHTML = `<tr><td colspan="${this._bulkMode ? 11 : 10}" class="tbl-empty">Aucun produit trouvé</td></tr>`;
            return;
        }

        const isAdmin  = (window.SM_ROLE === 'admin');
        const bulkMode = !!this._bulkMode;

        tbody.innerHTML = prods.map(p=>{
            const entries  = (this.stock||[]).filter(s=>String(s.productId)===String(p.id));
            const totalQty = entries.reduce((sum,s)=>sum+(parseInt(s.quantity)||0),0);
            let locLabel = '—';
            if (whFilter) {
                const e = entries.find(s=>String(s.warehouseId)===String(whFilter));
                const locId = e ? (e.locationId ?? '') : '';
                if (locId && this._locationsById && this._locationsById[String(locId)]) {
                    const l = this._locationsById[String(locId)];
                    locLabel = `${l.code || ''}${l.label ? ' · ' + l.label : ''}`.trim();
                } else if (e && e.locationLabel) {
                    locLabel = String(e.locationLabel);
                } else if (locId) {
                    locLabel = `#${locId}`;
                }
            }
            const baseCost = getBaseCost(p);
            const totalVal = totalQty * baseCost;
            const baseUnit = this._getBaseUnit(p);
            const unitLabel = baseUnit === 'piece' ? 'u' : baseUnit;
            const minStock  = entries.some(s=>s.minStock!=null)
                ? Math.min(...entries.filter(s=>s.minStock!=null).map(s=>parseInt(s.minStock)))
                : null;

            let status = '<span class="badge badge-success">OK</span>';
            if(minStock!=null && totalQty<=minStock)          status = '<span class="badge badge-danger">Critique</span>';
            else if(minStock!=null && totalQty<=minStock*1.5) status = '<span class="badge badge-warning">Bas</span>';

            // Colonne Magasin
            let aw = p.allowedWarehouses;
            if(typeof aw === 'string') try { aw = JSON.parse(aw||'[]'); } catch(e){ aw=[]; }
            const whNames = (Array.isArray(aw) && aw.length)
                ? aw.map(wid => (this.whs.find(w=>String(w.id)===String(wid))||{}).name||'?').join(', ')
                : '—';

            const actionBtn = isAdmin
                ? `<button class="btn btn-sm btn-secondary" onclick="event.stopPropagation();ProdPage.quickAction('${p.id}')">${Icons.move}</button>`
                : '';

            const checkCell = bulkMode
                ? `<td class="cell-check" onclick="event.stopPropagation()" style="width:36px">
                    <input type="checkbox" class="prod-chk" data-pid="${p.id}"
                      ${(this._bulkSelected||new Set()).has(String(p.id))?'checked':''}>
                   </td>`
                : '';

            return `<tr onclick="${bulkMode?'':'ProdPage.openModal(\'' + p.id + '\')'}" style="cursor:${bulkMode?'default':'pointer'}">
                ${checkCell}
                <td>
                    <div style="display:flex;align-items:center;gap:10px">
                        ${p.image ? `<span class="prod-list-img-trigger" style="display:inline-flex;width:40px;height:40px;border-radius:8px;overflow:hidden;flex-shrink:0;cursor:zoom-in;border:1px solid var(--border-light)"><img src="${p.image}" style="width:100%;height:100%;object-fit:cover" alt="${p.name}" onerror="this.parentElement.style.display='none'"></span>` : ''}
                        <strong>${p.name}</strong>
                    </div>
                </td>
                <td>${p.sku||'—'}</td>
                <td style="font-size:.85em;color:var(--text-muted);max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${whNames}">${whNames}</td>
                <td>${locLabel||'—'}</td>
                <td>${p.category||'—'}</td>
                <td>${p.brand||'—'}</td>
                <td class="cell-number">${totalQty} ${unitLabel}</td>
                <td class="cell-number">${totalVal>0?totalVal.toFixed(2)+'€':'—'}</td>
                <td>${status}</td>
                <td class="cell-actions">${actionBtn}</td>
            </tr>`;
        }).join('');
        this._attachImageHover();
        if(bulkMode) this._bindRowCheckboxes();
    },

    // ═══════════════════════════════════════════════════════════════
    // ÉDITION DE MASSE
    // ═══════════════════════════════════════════════════════════════

    _initBulkMode(){
        this._bulkMode    = false;
        this._bulkSelected = new Set();

        const btnToggle  = document.getElementById('btnToggleBulk');
        const bulkBar    = document.getElementById('prodBulkBar');
        const checkAll   = document.getElementById('checkAll');
        const thCheck    = document.getElementById('thCheck');
        const btnApply   = document.getElementById('btnBulkApply');
        const btnCancel  = document.getElementById('btnBulkCancel');

        if(!btnToggle) return;

        // Montrer le bouton seulement en vue liste
        this._syncBulkToggleVisibility();

        btnToggle.onclick = () => {
            this._bulkMode = !this._bulkMode;
            this._bulkSelected.clear();
            btnToggle.classList.toggle('btn-primary', this._bulkMode);
            btnToggle.classList.toggle('btn-ghost', !this._bulkMode);
            if(thCheck) thCheck.style.display = this._bulkMode ? '' : 'none';
            if(bulkBar) bulkBar.style.display  = this._bulkMode ? 'flex' : 'none';
            this._updateBulkCount();
            this.renderPaged();
        };

        if(checkAll) checkAll.onchange = (e) => {
            const cbs = document.querySelectorAll('.prod-chk');
            cbs.forEach(cb => {
                cb.checked = e.target.checked;
                const pid = cb.dataset.pid;
                if(e.target.checked) this._bulkSelected.add(pid);
                else this._bulkSelected.delete(pid);
            });
            this._updateBulkCount();
        };

        if(btnCancel) btnCancel.onclick = () => {
            this._bulkMode = false;
            this._bulkSelected.clear();
            if(btnToggle){ btnToggle.classList.remove('btn-primary'); btnToggle.classList.add('btn-ghost'); }
            if(thCheck) thCheck.style.display = 'none';
            if(bulkBar) bulkBar.style.display  = 'none';
            if(checkAll) checkAll.checked = false;
            this.renderPaged();
        };

        if(btnApply) btnApply.onclick = () => this._applyBulkEdit();

        // ── Peuplement initial Magasin ────────────────────────────
        const bulkWh    = document.getElementById('bulkWh');
        const bulkLoc   = document.getElementById('bulkLoc');
        const bulkCat   = document.getElementById('bulkCat');
        const bulkBrand = document.getElementById('bulkBrand');

        if(bulkWh){
            bulkWh.innerHTML = '<option value="">— Changer magasin —</option>' +
                (this.whs||[]).map(w=>`<option value="${w.id}">${w.name}</option>`).join('');

            // Cascade ① : Magasin → recalculer Emplacements + Catégories + Marques
            bulkWh.onchange = () => {
                this._rebuildBulkLoc();
                this._rebuildBulkCat();
            };
        }

        // Cascade ② : Catégorie → recalculer Marques
        if(bulkCat) bulkCat.onchange = () => this._rebuildBulkBrand();

        // Peuplement initial (tous produits visibles, pas de filtre)
        this._rebuildBulkLoc();
        this._rebuildBulkCat();
    },

    // Recalcule les catégories disponibles selon le magasin bulk sélectionné
    _rebuildBulkCat(){
        const bulkWh  = document.getElementById('bulkWh');
        const bulkCat = document.getElementById('bulkCat');
        if(!bulkCat) return;

        const whId = bulkWh ? String(bulkWh.value) : '';
        let cats = [];

        if(whId){
            // Utiliser les catégories définies sur le magasin (warehouses.json → wh.categories)
            const wh = (this.whs||[]).find(w => String(w.id) === whId);
            if(wh){
                let whCats = wh.categories;
                if(typeof whCats === 'string') try { whCats = JSON.parse(whCats||'[]'); } catch(e){ whCats=[]; }
                cats = Array.isArray(whCats) ? [...whCats].sort() : [];
            }
        } else {
            // Pas de magasin sélectionné → toutes les catégories de tous les magasins
            cats = [...new Set(
                (this.whs||[]).flatMap(w => {
                    let wc = w.categories;
                    if(typeof wc === 'string') try { wc = JSON.parse(wc||'[]'); } catch(e){ wc=[]; }
                    return Array.isArray(wc) ? wc : [];
                })
            )].sort();
        }

        const cur = bulkCat.value;
        bulkCat.innerHTML = '<option value="">— Changer catégorie —</option>' +
            cats.map(c=>`<option value="${c}"${c===cur?' selected':''}>${c}</option>`).join('');

        // Recalcule aussi les marques
        this._rebuildBulkBrand();
    },

    // Recalcule les emplacements disponibles selon le magasin bulk sélectionné
    async _rebuildBulkLoc(){
        const bulkWh  = document.getElementById('bulkWh');
        const bulkLoc = document.getElementById('bulkLoc');
        if(!bulkLoc) return;

        const whId = bulkWh ? String(bulkWh.value) : '';
        const cur = String(bulkLoc.value || '');

        // Sans magasin => désactiver (source d'erreurs et options incohérentes)
        if(!whId){
            bulkLoc.disabled = true;
            bulkLoc.innerHTML = '<option value="">— Changer emplacement —</option>';
            return;
        }

        bulkLoc.disabled = true;
        bulkLoc.innerHTML = '<option value="">— Chargement… —</option>';

        try {
            // Même source que la modale produit : Api.locations(warehouseId)
            const locs = await Api.locations(parseInt(whId,10));
            const active = (Array.isArray(locs) ? locs : []).filter(l => l && l.active !== false);

            if(active.length === 0){
                bulkLoc.innerHTML = '<option value="">— Aucun emplacement —</option>';
                bulkLoc.disabled = false;
                return;
            }

            // 0 => retirer l'emplacement (stock par magasin)
            bulkLoc.innerHTML =
                '<option value="">— Changer emplacement —</option>' +
                '<option value="0">— Aucun emplacement spécifique —</option>' +
                active.map(l => {
                    const label = [l.code, l.label, l.zone].filter(Boolean).join(' · ');
                    return `<option value="${l.id}">${label}</option>`;
                }).join('');

            // Restaurer la sélection si possible
            if(cur && [...bulkLoc.options].some(o => String(o.value) === cur)){
                bulkLoc.value = cur;
            } else {
                bulkLoc.value = '';
            }
        } catch(e){
            console.error('Bulk locations load error:', e);
            bulkLoc.innerHTML = '<option value="">— Erreur chargement —</option>';
        } finally {
            bulkLoc.disabled = false;
        }
    },

    // Recalcule les marques disponibles selon le magasin + catégorie bulk sélectionnés
    _rebuildBulkBrand(){
        const bulkWh    = document.getElementById('bulkWh');
        const bulkLoc   = document.getElementById('bulkLoc');
        const bulkCat   = document.getElementById('bulkCat');
        const bulkBrand = document.getElementById('bulkBrand');
        if(!bulkBrand) return;

        const whId = bulkWh  ? String(bulkWh.value)  : '';
        const catV = bulkCat ? bulkCat.value          : '';

        // Utiliser TOUS les produits (pas seulement ceux affichés)
        let prodsInScope = this._allProds || this._shown || [];

        if(whId){
            prodsInScope = prodsInScope.filter(p => {
                let aw = p.allowedWarehouses;
                if(typeof aw === 'string') try { aw = JSON.parse(aw||'[]'); } catch(e){ aw=[]; }
                return Array.isArray(aw) && aw.some(a => String(a) === whId);
            });
        }
        if(catV) prodsInScope = prodsInScope.filter(p => p.category === catV);

        const brands = [...new Set(prodsInScope.map(p=>p.brand).filter(Boolean))].sort();
        const cur    = bulkBrand.value;
        bulkBrand.innerHTML = '<option value="">— Changer marque —</option>' +
            brands.map(b=>`<option value="${b}"${b===cur?' selected':''}>${b}</option>`).join('');
    },

    _syncBulkToggleVisibility(){
        const btn = document.getElementById('btnToggleBulk');
        if(btn) btn.style.display = (this._currentView === 'list') ? '' : 'none';
        // Fermer le mode bulk si on quitte la vue liste
        if(this._bulkMode && this._currentView !== 'list'){
            const btnCancel = document.getElementById('btnBulkCancel');
            if(btnCancel) btnCancel.click();
        }
    },

    _bindRowCheckboxes(){
        document.querySelectorAll('.prod-chk').forEach(cb => {
            cb.onchange = (e) => {
                const pid = e.target.dataset.pid;
                if(e.target.checked) this._bulkSelected.add(pid);
                else this._bulkSelected.delete(pid);
                this._updateBulkCount();
                // Sync "tout sélectionner"
                const all = document.querySelectorAll('.prod-chk');
                const allChecked = [...all].every(c=>c.checked);
                const chkAll = document.getElementById('checkAll');
                if(chkAll) chkAll.checked = allChecked;
            };
        });
    },

    _updateBulkCount(){
        const el = document.getElementById('prodBulkCount');
        if(el) el.textContent = `${this._bulkSelected.size} sélectionné(s)`;
    },

    async _applyBulkEdit(){
        if(!this._bulkSelected.size){
            toast('Aucun produit sélectionné', 'warning');
            return;
        }
        const cat   = document.getElementById('bulkCat')?.value   || '';
        const brand = document.getElementById('bulkBrand')?.value || '';
        const whId  = document.getElementById('bulkWh')?.value    || '';
        const locV  = document.getElementById('bulkLoc')?.value   || '';

        if(!cat && !brand && !whId && !locV){
            toast('Choisissez au moins un champ à modifier', 'warning');
            return;
        }
        // Emplacement nécessite un magasin (stock par magasin)
        if(locV && !whId){
            toast('Choisissez d’abord un magasin pour modifier l’emplacement', 'warning');
            return;
        }

        const ids = [...this._bulkSelected];
        let ok = 0, ko = 0;

        for(const pid of ids){
            const prod = (this._shown||[]).find(p=>String(p.id)===String(pid));
            if(!prod) continue;

            const fields = {};
            if(cat)   fields.category = cat;
            if(brand) fields.brand    = brand;
            if(whId){
                // Remplacer (pas ajouter) le magasin
                fields.allowedWarehouses = [parseInt(whId)];
            }

            try {
                // prodEdit nécessite l'objet produit complet ou au minimum id + champs
                // On envoie uniquement les champs à modifier + l'id
                await Api.prodEdit({ id: pid, ...fields });

                // Emplacement (stock) : uniquement si demandé
                if(locV !== ''){
                    const locationId = (String(locV) === '0') ? null : parseInt(locV, 10);
                    await Api.stockSet({
                        productId: parseInt(pid, 10),
                        warehouseId: parseInt(whId, 10),
                        locationId: locationId
                    });
                }

                ok++;
            } catch(e){
                ko++;
                console.error('Bulk update error:', e, pid);
            }
        }

        toast(`${ok} produit(s) mis à jour${ko>0?' — '+ko+' erreur(s)':''}`, ok>0?'success':'error');

        // Reset + reload
        this._bulkSelected.clear();
        this._updateBulkCount();
        document.querySelectorAll('.prod-chk').forEach(cb=>cb.checked=false);
        const chkAll = document.getElementById('checkAll'); if(chkAll) chkAll.checked=false;
        await this.load();
    },

    _buildWarehouseBadges(productId, whFilter){
        const stock = this.stock || [];
        const whs = this.whs || [];
        const prods = this._shown || [];
        
        // Trouver le produit pour récupérer allowedWarehouses
        const product = prods.find(p => String(p.id) === String(productId));
        
        // Stock par magasin
        const byWh = new Map();
        for(const s of stock){
            if(String(s.productId) !== String(productId)) continue;
            const wid = String(s.warehouseId||'');
            const qty = parseFloat(s.quantity)||0;
            byWh.set(wid, (byWh.get(wid)||0) + qty);
        }
        
        // Ajouter aussi les magasins autorisés même sans stock
        if(product && product.allowedWarehouses) {
            let allowed = product.allowedWarehouses;
            if(typeof allowed === 'string') {
                try { allowed = JSON.parse(allowed || '[]'); } catch(e) { allowed = []; }
            }
            if(Array.isArray(allowed)) {
                for(const wid of allowed) {
                    const widStr = String(wid);
                    if(!byWh.has(widStr)) {
                        byWh.set(widStr, 0);
                    }
                }
            }
        }

        const entries = Array.from(byWh.entries()).map(([wid,qty])=>{
            const w = whs.find(x=>String(x.id)===String(wid));
            return { wid, qty, name: w ? w.name : ('Magasin '+wid) };
        }).sort((a,b)=>b.qty-a.qty);

        if(whFilter){
            const e = entries.find(x=>String(x.wid)===String(whFilter));
            if(!e) return '';
            const badgeClass = e.qty > 0 ? 'badge-blue' : 'badge-gray';
            return `<span class="badge ${badgeClass}" style="font-size:10px">${e.name} · ${e.qty}u</span>`;
        }

        if(!entries.length) return '';
        const top = entries.slice(0,3).map(e=>{
            const badgeClass = e.qty > 0 ? 'badge-blue' : 'badge-gray';
            return `<span class="badge ${badgeClass}" style="font-size:10px">${e.name} · ${e.qty}u</span>`;
        }).join(' ');
        const more = entries.length>3 ? `<span class="badge badge-gray" style="font-size:10px">+${entries.length-3}</span>` : '';
        return `<div class="prod-wh-badges" style="margin-top:6px;display:flex;gap:6px;flex-wrap:wrap">${top} ${more}</div>`;
    },

    // Version sans unité dans les badges magasin (unité déjà affichée sous la barre de stock)
    _buildWarehouseBadgesNoUnit(productId, whFilter){
        const stock = this.stock || [];
        const whs   = this.whs   || [];
        const byWh  = new Map();
        for(const s of stock){
            if(String(s.productId)!==String(productId)) continue;
            const wid = String(s.warehouseId||'');
            byWh.set(wid, (byWh.get(wid)||0) + (parseFloat(s.quantity)||0));
        }
        const entries = Array.from(byWh.entries()).map(([wid,qty])=>{
            const w = whs.find(x=>String(x.id)===String(wid));
            return { wid, qty, name: w ? w.name : ('Mag.'+wid) };
        }).sort((a,b)=>b.qty-a.qty);

        if(whFilter){
            const e = entries.find(x=>String(x.wid)===String(whFilter));
            if(!e) return '';
            const cls = e.qty > 0 ? 'badge-blue' : 'badge-muted';
            return `<span class="badge ${cls} prod-badge-wh">${e.name}</span>`;
        }
        if(!entries.length) return '';
        const top = entries.slice(0,2).map(e=>{
            const cls = e.qty > 0 ? 'badge-blue' : 'badge-muted';
            return `<span class="badge ${cls} prod-badge-wh">${e.name}</span>`;
        }).join('');
        const more = entries.length>2 ? `<span class="badge badge-muted prod-badge-wh">+${entries.length-2}</span>` : '';
        return top + more;
    },

renderGrid(prods, whFilter){
        const grid = document.getElementById('prodGrid');
        if(!prods.length){ grid.innerHTML='<div class="tbl-empty" style="grid-column:1/-1;padding:40px">Aucun produit trouvé</div>'; return; }
        this._ensureCardStyles();
        grid.innerHTML = this._buildGridHTML(prods, whFilter);
        this._attachImageHover();
    },
    
    _renderGridItems(prods, whFilter, append = false){
        const grid = document.getElementById('prodGrid');
        if(!grid) return;
        if(!prods.length && !append){ 
            grid.innerHTML='<div class="tbl-empty" style="grid-column:1/-1;padding:40px">Aucun produit trouvé</div>'; 
            return; 
        }
        this._ensureCardStyles();
        const html = this._buildGridHTML(prods, whFilter);
        if(append){
            grid.insertAdjacentHTML('beforeend', html);
        } else {
            grid.innerHTML = html;
        }
        this._attachImageHover();
    },

    _ensureCardStyles(){
        if(document.getElementById('pc2-card-css')) return;
        const s = document.createElement('style');
        s.id = 'pc2-card-css';
        s.textContent = `
/* ═══ PRODUCT CARDS v3 — Injection JS — Priorité absolue ═══ */
#prodGrid.products-grid,#prodGrid.prod-grid{grid-template-columns:repeat(auto-fill,minmax(220px,1fr))!important;gap:16px!important}
#prodGrid .pc2{background:var(--bg-card,#fff)!important;border:2px solid var(--border,#e2e8f0)!important;border-radius:14px!important;cursor:pointer!important;position:relative!important;display:flex!important;flex-direction:column!important;overflow:hidden!important;padding:0!important;gap:0!important;transition:transform .22s,box-shadow .22s,border-color .22s!important}
#prodGrid .pc2:hover{transform:translateY(-4px)!important;border-color:var(--blue,#3b82f6)!important;box-shadow:0 14px 32px rgba(0,0,0,.10),0 0 0 1px var(--blue,#3b82f6)!important}
#prodGrid .pc2:active{transform:translateY(-1px)!important}
#prodGrid .pc2-hero{position:relative!important;width:100%!important;aspect-ratio:1/1!important;background:var(--bg-elevated,#f8fafc)!important;display:flex!important;align-items:center!important;justify-content:center!important;overflow:hidden!important;border-bottom:1px solid var(--border-light,rgba(0,0,0,.04))!important;border-radius:0!important;flex-shrink:0!important;min-height:160px!important}
#prodGrid .pc2-hero-img{width:100%!important;height:100%!important;object-fit:contain!important;padding:14px!important;transition:transform .3s!important;display:block!important}
#prodGrid .pc2:hover .pc2-hero-img{transform:scale(1.06)!important}
#prodGrid .pc2-hero--empty{background:var(--bg-elevated,#f8fafc)!important}
#prodGrid .pc2-hero-svg{width:52px!important;height:52px!important;fill:none!important;stroke:var(--text-muted,#94a3b8)!important;stroke-width:1!important;opacity:.18!important}
#prodGrid .pc2-qa{position:absolute!important;top:8px!important;right:8px!important;z-index:2!important;width:34px!important;height:34px!important;border-radius:10px!important;background:var(--bg-card,#fff)!important;border:1px solid var(--border,#e2e8f0)!important;display:flex!important;align-items:center!important;justify-content:center!important;color:var(--text-muted,#94a3b8)!important;cursor:pointer!important;opacity:0!important;transition:all .15s!important;box-shadow:0 2px 8px rgba(0,0,0,.08)!important;padding:0!important}
#prodGrid .pc2:hover .pc2-qa{opacity:1!important}
#prodGrid .pc2-qa:hover{background:var(--blue-pale,#dbeafe)!important;border-color:var(--blue)!important;color:var(--blue)!important}
#prodGrid .pc2-qa svg{width:16px!important;height:16px!important}
#prodGrid .pc2-flag{position:absolute!important;top:8px!important;left:8px!important;z-index:2!important;padding:3px 10px!important;border-radius:6px!important;font-size:10px!important;font-weight:700!important;letter-spacing:.5px!important;text-transform:uppercase!important}
#prodGrid .pc2-flag--crit{background:var(--red,#ef4444)!important;color:#fff!important}
#prodGrid .pc2-flag--low{background:var(--orange,#f59e0b)!important;color:#fff!important}
#prodGrid .pc2-identity{padding:12px 14px 4px!important}
#prodGrid .pc2-name{font-size:14px!important;font-weight:700!important;color:var(--text-primary,#0f172a)!important;line-height:1.25!important;margin:0 0 3px!important;padding:0!important;display:-webkit-box!important;-webkit-line-clamp:2!important;-webkit-box-orient:vertical!important;overflow:hidden!important}
#prodGrid .pc2-sku{font-family:var(--font-mono,'DM Mono',monospace)!important;font-size:11px!important;color:var(--text-muted,#94a3b8)!important;letter-spacing:.3px!important}
#prodGrid .pc2-chips{display:flex!important;flex-wrap:wrap!important;gap:4px!important;padding:6px 14px 2px!important}
#prodGrid .pc2-chip{display:inline-flex!important;align-items:center!important;padding:2px 8px!important;border-radius:6px!important;font-size:10.5px!important;font-weight:600!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;max-width:140px!important;line-height:1.5!important}
#prodGrid .pc2-chip--brand{background:var(--blue-pale,#dbeafe)!important;color:var(--blue,#3b82f6)!important}
#prodGrid .pc2-chip--cat{background:var(--bg-elevated,#f1f5f9)!important;color:var(--text-secondary,#64748b)!important;border:1px solid var(--border-light,rgba(0,0,0,.06))!important}
#prodGrid .pc2-chip--loc{background:var(--green-pale,#dcfce7)!important;color:var(--green,#16a34a)!important}
#prodGrid .pc2-wh{display:flex!important;flex-wrap:wrap!important;gap:4px!important;padding:4px 14px 0!important}
#prodGrid .pc2-bottom{padding:10px 14px 12px!important;margin-top:auto!important}
#prodGrid .pc2-bar{height:5px!important;background:var(--bg-elevated,#f1f5f9)!important;border-radius:99px!important;overflow:hidden!important;margin-bottom:8px!important}
#prodGrid .pc2-bar-fill{height:100%!important;border-radius:99px!important;transition:width .3s!important}
#prodGrid .pc2-bar--ok{background:linear-gradient(90deg,#22c55e,#86efac)!important}
#prodGrid .pc2-bar--low{background:linear-gradient(90deg,#f59e0b,#fcd34d)!important}
#prodGrid .pc2-bar--crit{background:linear-gradient(90deg,#ef4444,#fca5a5)!important}
#prodGrid .pc2-stats{display:flex!important;align-items:baseline!important;justify-content:space-between!important;gap:6px!important}
#prodGrid .pc2-qty{font-size:16px!important;font-weight:800!important;letter-spacing:-.5px!important}
#prodGrid .pc2-qty--crit{color:var(--red,#ef4444)!important}
#prodGrid .pc2-qty--low{color:var(--orange,#f59e0b)!important}
#prodGrid .pc2-qty--ok{color:var(--text-primary,#0f172a)!important}
#prodGrid .pc2-val{font-size:12px!important;color:var(--text-muted,#94a3b8)!important;font-weight:500!important}
#prodGrid .pc2-docs{display:flex!important;gap:6px!important;padding:0 14px 10px!important;border-top:1px solid var(--border-light,rgba(0,0,0,.04))!important;padding-top:8px!important}
#prodGrid .pc2-doc{font-size:10.5px!important;padding:3px 8px!important;border-radius:6px!important;background:var(--bg-elevated,#f1f5f9)!important;color:var(--text-secondary,#64748b)!important;text-decoration:none!important;font-weight:500!important;border:1px solid transparent!important;transition:all .12s!important}
#prodGrid .pc2-doc:hover{background:var(--blue-pale)!important;border-color:var(--blue)!important;color:var(--blue)!important}
@media(max-width:1024px){#prodGrid.products-grid,#prodGrid.prod-grid,.products-grid,.prod-grid{grid-template-columns:repeat(auto-fill,minmax(180px,1fr))!important;gap:12px!important}#prodGrid .pc2-hero{aspect-ratio:1/1!important}#prodGrid .pc2-hero-img{padding:10px!important}#prodGrid .pc2-identity{padding:10px 12px 3px!important}#prodGrid .pc2-name{font-size:13px!important}#prodGrid .pc2-chips{padding:4px 12px 2px!important}#prodGrid .pc2-wh{padding:3px 12px 0!important}#prodGrid .pc2-bottom{padding:8px 12px 10px!important}#prodGrid .pc2-qty{font-size:14px!important}}
@media(max-width:640px){#prodGrid.products-grid,#prodGrid.prod-grid,.products-grid,.prod-grid{grid-template-columns:repeat(2,1fr)!important;gap:8px!important}#prodGrid .pc2{border-radius:12px!important;border-width:1px!important}#prodGrid .pc2-hero{min-height:120px!important}#prodGrid .pc2-hero-img{padding:6px!important}#prodGrid .pc2-hero-svg{width:36px!important;height:36px!important}#prodGrid .pc2-identity{padding:8px 10px 2px!important}#prodGrid .pc2-name{font-size:12px!important}#prodGrid .pc2-sku{font-size:9.5px!important}#prodGrid .pc2-chips{padding:4px 10px 0!important;gap:3px!important}#prodGrid .pc2-chip{font-size:9px!important;padding:1px 5px!important;max-width:110px!important}#prodGrid .pc2-wh{padding:2px 10px 0!important}#prodGrid .pc2-bottom{padding:6px 10px 8px!important}#prodGrid .pc2-bar{height:4px!important;margin-bottom:6px!important}#prodGrid .pc2-qty{font-size:13px!important}#prodGrid .pc2-val{font-size:10px!important}#prodGrid .pc2-docs{padding:0 10px 8px!important;padding-top:6px!important}#prodGrid .pc2-qa{opacity:1!important;width:28px!important;height:28px!important;top:5px!important;right:5px!important;border-radius:8px!important}#prodGrid .pc2-qa svg{width:14px!important;height:14px!important}#prodGrid .pc2-flag{font-size:8px!important;padding:2px 6px!important;top:5px!important;left:5px!important}}
@media(max-width:380px){#prodGrid.products-grid,#prodGrid.prod-grid,.products-grid,.prod-grid{grid-template-columns:1fr!important}#prodGrid .pc2-hero{aspect-ratio:16/10!important}}
`;
        document.head.appendChild(s);
    },
    
    /**
     * Construit le HTML pour les cartes produits
     */
    _buildGridHTML(prods, whFilter){
        const isAdmin = (window.SM_ROLE === 'admin');
        return prods.map(p=>{
            const entries = (this.stock||[]).filter(s=>String(s.productId)===String(p.id));
            const totalQty = entries.reduce((sum,s)=>sum+(parseInt(s.quantity)||0),0);
            let locLabel = '';
            if (whFilter) {
                const e = entries.find(s=>String(s.warehouseId)===String(whFilter));
                const locId = e ? (e.locationId ?? '') : '';
                if (locId && this._locationsById && this._locationsById[String(locId)]) {
                    const l = this._locationsById[String(locId)];
                    locLabel = (l.code || '') + (l.label ? ' · ' + l.label : '');
                } else if (e && e.locationLabel) {
                    locLabel = String(e.locationLabel);
                }
            }
            const baseCost = getBaseCost(p);
            const totalVal = totalQty * baseCost;
            const baseUnit = this._getBaseUnit(p);
            const unitLabel = baseUnit === 'piece' ? 'u' : baseUnit;
            const minStock = entries.some(s=>s.minStock!=null) ? Math.min(...entries.filter(s=>s.minStock!=null).map(s=>parseInt(s.minStock))) : null;
            const maxAuth = entries.some(s=>s.maxAuthorized!=null && s.maxAuthorized<9990) ? Math.max(...entries.filter(s=>s.maxAuthorized!=null).map(s=>parseInt(s.maxAuthorized))) : null;
            const barRef = maxAuth || (minStock ? minStock * 3 : null);
            const barPct = barRef && barRef > 0 ? Math.min(100, Math.round((totalQty / barRef) * 100)) : (totalQty > 0 ? 60 : 0);
            let barCls = 'ok';
            if(totalQty === 0) barCls = 'crit';
            else if(minStock!=null && totalQty<=minStock) barCls='crit';
            else if(minStock!=null && totalQty<=minStock*1.5) barCls='low';

            const hasImg = !!p.image;
            const supplier = p.supplier ? (this.suppliers||[]).find(s=>String(s.id)===String(p.supplier)) : null;
            const whBadges = ProdPage._buildWarehouseBadgesNoUnit(p.id, whFilter);
            const qaBtn = isAdmin ? '<button class="pc2-qa" onclick="event.stopPropagation();ProdPage.quickAction(\''+p.id+'\')" title="Mouvement rapide">'+Icons.move+'</button>' : '';

            let statusHtml = '';
            if(barCls === 'crit') statusHtml = '<span class="pc2-flag pc2-flag--crit">CRITIQUE</span>';
            else if(barCls === 'low') statusHtml = '<span class="pc2-flag pc2-flag--low">STOCK BAS</span>';

            let html = '<div class="pc2" onclick="ProdPage.openModal(\''+p.id+'\')">';

            // ── HERO IMAGE (60% de la carte) ──
            html += '<div class="pc2-hero prod-thumb-zoomable'+(hasImg?'':' pc2-hero--empty')+'">';
            if(hasImg) html += '<img src="'+p.image+'" alt="'+p.name+'" class="pc2-hero-img" onerror="this.parentElement.classList.add(\'pc2-hero--empty\');this.remove()">';
            else html += '<svg viewBox="0 0 24 24" class="pc2-hero-svg"><path d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z"/></svg>';
            html += qaBtn + statusHtml + '</div>';

            // ── NOM + SKU ──
            html += '<div class="pc2-identity">';
            html += '<h4 class="pc2-name">'+p.name+'</h4>';
            html += '<span class="pc2-sku">'+(p.sku||'')+'</span>';
            html += '</div>';

            // ── BADGES : marque + catégorie + emplacement ──
            const chips = [];
            if(p.brand) chips.push('<span class="pc2-chip pc2-chip--brand">'+p.brand+'</span>');
            if(p.category) chips.push('<span class="pc2-chip pc2-chip--cat">'+p.category+'</span>');
            if(locLabel) chips.push('<span class="pc2-chip pc2-chip--loc">\u{1F4CD} '+locLabel+'</span>');
            if(chips.length) html += '<div class="pc2-chips">'+chips.join('')+'</div>';

            // ── MAGASINS ──
            if(whBadges) html += '<div class="pc2-wh">'+whBadges+'</div>';

            // ── STOCK BAR + QUANTITÉ + VALEUR ──
            html += '<div class="pc2-bottom">';
            html += '<div class="pc2-bar"><div class="pc2-bar-fill pc2-bar--'+barCls+'" style="width:'+barPct+'%"></div></div>';
            html += '<div class="pc2-stats">';
            html += '<span class="pc2-qty pc2-qty--'+barCls+'">'+totalQty+' '+unitLabel+'</span>';
            if(totalVal > 0) html += '<span class="pc2-val">'+totalVal.toFixed(2)+' \u20ac</span>';
            html += '</div>';
            html += '</div>';

            // ── DOCUMENTS ──
            if(p.datasheetUrl || p.msdsUrl){
                html += '<div class="pc2-docs">';
                if(p.datasheetUrl) html += '<a href="'+p.datasheetUrl+'" target="_blank" onclick="event.stopPropagation()" class="pc2-doc">\u{1F4C4} FT</a>';
                if(p.msdsUrl) html += '<a href="'+p.msdsUrl+'" target="_blank" onclick="event.stopPropagation()" class="pc2-doc">\u26A0\uFE0F FDS</a>';
                html += '</div>';
            }

            html += '</div>';
            return html;
        }).join('');
    },
    async quickAction(pid){
        const p = await Api.products().then(all=>all.find(x=>String(x.id)===String(pid)));
        if(!p){ toast('Produit introuvable','warning'); return; }

        const [stockAll] = await Promise.all([Api.stock(pid)]);
        const whs = this.whs;
        const baseUnit = this._getBaseUnit(p);
        const needsLot = !!p.trackLot;
        const needsExp = !!p.trackExpiry;
        const unitOpts = this._buildUnitOptions(p);
        const unitCostPrefill = this._getUnitCostPrefill(p, baseUnit);

        // Stock par magasin
        const stockMap = {};
        (stockAll||[]).forEach(s => { stockMap[String(s.warehouseId)] = Number(s.quantity)||0; });

        // Filtrer les magasins par allowedWarehouses du produit
        let allowed = p.allowedWarehouses;
        if (typeof allowed === 'string') try { allowed = JSON.parse(allowed||'[]'); } catch(e) { allowed = []; }
        const allowedStrs = Array.isArray(allowed) ? allowed.map(String) : [];
        const eligibleWhs = (!allowedStrs.length)
            ? whs
            : whs.filter(w => allowedStrs.includes(String(w.id)));

        // Options magasin avec stock visible
        const whOpts = eligibleWhs.map(w => {
            const qty = stockMap[String(w.id)] || 0;
            const lbl = qty > 0 ? ` — ${qty} ${baseUnit}` : ' — aucun stock';
            return `<option value="${w.id}">${w.name}${lbl}</option>`;
        }).join('');

        const traceBlock = (needsLot || needsExp) ? `
            <div class="sq-sep"></div>
            <div class="sq-section-title">📋 Traçabilité</div>
            <div class="sq-field-group">
                <label class="lbl">Numéro de lot${needsLot?' *':''}</label>
                <input type="text" class="inp" id="qaLot" placeholder="Ex: LOT-2026-001">
            </div>
            <div class="sq-field-group">
                <label class="lbl">DLUO / Péremption${needsExp?' *':''}</label>
                <input type="date" class="inp" id="qaExp">
            </div>` : '';

        Modal.show('Mouvement rapide — '+p.name, `
<div class="sq-form">

  <!-- Produit banner -->
  <div class="sq-product-banner">
    <div class="sq-product-icon">📦</div>
    <div class="sq-product-info">
      <div class="sq-product-name">${p.name||''}</div>
      <div class="sq-product-sku">${p.sku||p.barcode||''}</div>
    </div>
  </div>

  <!-- ① Magasin -->
  <div class="sq-step" id="qsStepWh">
    <div class="sq-step-header">
      <span class="sq-step-num">1</span>
      <span class="sq-step-label">Magasin *</span>
      <span class="sq-stock-badge" id="qsStockBadge" style="display:none"></span>
    </div>
    <select class="sel" id="qaProdWh">
      <option value="">— Choisir un magasin —</option>
      ${whOpts}
    </select>
  </div>

  <!-- ② Mouvement (activé après magasin) -->
  <div class="sq-step sq-step-locked" id="qsStepAction">
    <div class="sq-step-header">
      <span class="sq-step-num">2</span>
      <span class="sq-step-label">Type de mouvement</span>
    </div>
    <div style="display:flex;gap:8px">
      <button class="btn btn-success" style="flex:1" id="qsBtnAdd" disabled
        onclick="ProdPage._qaOpenType('add')">＋ Ajout de stock</button>
      <button class="btn btn-danger" style="flex:1" id="qsBtnRem" disabled
        onclick="ProdPage._qaOpenType('remove')">− Sortie de stock</button>
    </div>
  </div>

  <!-- ③ Quantité (activé après choix du type) -->
  <div class="sq-step sq-step-locked" id="qsStepQty">
    <div class="sq-step-header">
      <span class="sq-step-num">3</span>
      <span class="sq-step-label">Quantité & Conditionnement</span>
    </div>
    <div class="sq-qty-row">
      <div>
        <label class="lbl">Unité</label>
        <select class="sel" id="qaUnit" disabled>${unitOpts}</select>
      </div>
      <div>
        <label class="lbl">Quantité *</label>
        <input type="number" class="inp" id="qaQty" min="0.01" step="0.01" value="1" disabled>
      </div>
    </div>
    <small class="sq-conv-hint">Conversion automatique vers l'unité de base (${baseUnit}).</small>
    <div class="sq-field-group" id="qsCostWrap">
      <label class="lbl">Prix d'achat (optionnel)</label>
      <input type="number" class="inp" id="qaUnitCost" min="0" step="0.0001" value="${unitCostPrefill}" disabled>
    </div>
  </div>

  <!-- ④ Détails -->
  <div class="sq-step sq-step-locked" id="qsStepDetails">
    <div class="sq-step-header">
      <span class="sq-step-num">4</span>
      <span class="sq-step-label">Détails</span>
    </div>
    ${traceBlock}
    <div class="sq-field-group">
      <label class="lbl">Raison / Chantier</label>
      <input type="text" class="inp" id="qaSite" placeholder="Chantier / lieu…" disabled>
    </div>
    <div class="sq-field-group">
      <label class="lbl">Note</label>
      <input type="text" class="inp" id="qaNote" disabled>
    </div>
    <div style="margin-top:14px;display:flex;gap:8px">
      <button class="btn btn-success" style="flex:1;display:none" id="qaValAdd"
        onclick="ProdPage._qaSubmit('${pid}','add')">✓ Valider l'ajout</button>
      <button class="btn btn-danger" style="flex:1;display:none" id="qaValRem"
        onclick="ProdPage._qaSubmit('${pid}','remove')">✓ Valider la sortie</button>
    </div>
  </div>

</div>
        `, [{label:'Fermer',cls:'btn-secondary',onclick:()=>Modal.close()}], 'info');

        // CSS (partagé avec scanner.js quickMove)
        // ── Icône personnalisée mouvement rapide ────────────────────
        const mqIcon = document.getElementById('modalIcon');
        if(mqIcon){
            mqIcon.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 6 13.5 15.5 8.5 10.5 1 18"/><polyline points="17 6 23 6 23 12"/></svg>';
            const hdr = mqIcon.parentElement;
            if(hdr) hdr.style.background = 'var(--green-pale, #dcfce7)';
            const svg = mqIcon.querySelector('svg');
            if(svg) svg.style.stroke = 'var(--green, #22c55e)';
        }

        // CSS (partagé avec scanner.js quickMove)
        if(!document.getElementById('sq-modal-css')){
            const s=document.createElement('style'); s.id='sq-modal-css';
            s.textContent=`.sq-product-banner{display:flex;align-items:center;gap:12px;padding:12px 14px;background:var(--bg-elevated);border-radius:var(--r-lg,10px);border:1px solid var(--border);margin-bottom:2px}.sq-product-icon{width:36px;height:36px;border-radius:50%;background:var(--blue-pale,#eff6ff);color:var(--blue,#3b82f6);display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:700;flex-shrink:0}.sq-product-name{font-weight:600;font-size:15px;color:var(--text-primary)}.sq-product-sku{font-size:12px;color:var(--text-muted);font-family:monospace}.sq-form{display:flex;flex-direction:column;gap:6px}.sq-step{border:1px solid var(--border);border-radius:var(--r-lg,10px);padding:14px 16px;background:var(--bg-card);transition:opacity .2s,background .2s}.sq-step-locked{opacity:.42;pointer-events:none;background:var(--bg-elevated)}.sq-step-header{display:flex;align-items:center;gap:10px;margin-bottom:10px;flex-wrap:wrap}.sq-step-num{width:24px;height:24px;border-radius:50%;background:var(--blue,#3b82f6);color:#fff;font-size:12px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0}.sq-step-locked .sq-step-num{background:var(--text-muted,#94a3b8)}.sq-step-label{font-size:14px;font-weight:600;color:var(--text-primary);flex:1}.sq-stock-badge{font-size:12px;font-weight:600;padding:3px 10px;border-radius:999px}.sq-stock-ok{background:var(--green-pale,#dcfce7);color:var(--green,#16a34a)}.sq-stock-low{background:var(--yellow-pale,#fef9c3);color:#b45309}.sq-stock-zero{background:var(--red-pale,#fee2e2);color:var(--red,#dc2626)}.sq-qty-row{display:grid;grid-template-columns:1fr 2fr;gap:12px}@media(max-width:400px){.sq-qty-row{grid-template-columns:1fr}}.sq-conv-hint{display:block;font-size:12px;color:var(--text-muted);margin-top:6px;margin-bottom:4px}.sq-sep{height:1px;background:var(--border);margin:10px 0 4px}.sq-section-title{font-size:13px;font-weight:700;color:var(--text-secondary);margin-bottom:8px}.sq-field-group{margin-top:10px}.sq-field-group .lbl{display:block;margin-bottom:4px}`;
            document.head.appendChild(s);
        }

        // Stocker le contexte pour _qaOpenType
        this._qaCtx = { p, pid, baseUnit, needsLot, needsExp, stockMap };

        // Cascade magasin → débloquer étape 2
        const whSel = document.getElementById('qaProdWh');
        const badge = document.getElementById('qsStockBadge');
        const stepAction = document.getElementById('qsStepAction');
        const btnAdd = document.getElementById('qsBtnAdd');
        const btnRem = document.getElementById('qsBtnRem');

        if(whSel) whSel.addEventListener('change', () => {
            const wid = whSel.value;
            if(!wid) return;
            const qty = stockMap[String(wid)] || 0;

            if(badge){
                badge.style.display='';
                badge.className = `sq-stock-badge ${qty<=0?'sq-stock-zero':qty<(p.minStock||5)?'sq-stock-low':'sq-stock-ok'}`;
                badge.textContent = `Stock : ${qty} ${baseUnit}`;
            }
            if(stepAction) stepAction.classList.remove('sq-step-locked');
            if(btnAdd){ btnAdd.disabled=false; }
            if(btnRem){ btnRem.disabled = qty <= 0; }
        });

        // Unité prefill on change
        const unitSel = document.getElementById('qaUnit');
        const costInp = document.getElementById('qaUnitCost');
        if(unitSel) unitSel.onchange = () => { if(costInp) costInp.value = String(this._getUnitCostPrefill(p, unitSel.value)||0); };

        // Stocker eligibleWhs dans le contexte
        this._qaCtx.eligibleWhs = eligibleWhs;

        // Si 1 seul magasin : présélectionner
        if(eligibleWhs.length === 1){
            whSel.value = eligibleWhs[0].id;
            whSel.dispatchEvent(new Event('change'));
        }
    },

    _qaOpenType(type){
        const ctx = this._qaCtx;
        if(!ctx) return;
        const baseUnit = ctx.baseUnit;

        // Débloquer étapes 3 et 4
        const stepQty = document.getElementById('qsStepQty');
        const stepDetails = document.getElementById('qsStepDetails');
        if(stepQty) stepQty.classList.remove('sq-step-locked');
        if(stepDetails) stepDetails.classList.remove('sq-step-locked');

        ['qaUnit','qaQty','qaUnitCost','qaSite','qaNote','qaLot','qaExp'].forEach(id=>{
            const el=document.getElementById(id);
            if(el && el.disabled!==undefined) el.disabled=false;
        });

        // Unité par défaut
        const unitSel = document.getElementById('qaUnit');
        if(unitSel && [...unitSel.options].some(o=>o.value===baseUnit)) unitSel.value=baseUnit;

        // Masquer/afficher le bloc coût selon type
        const costWrap = document.getElementById('qsCostWrap');
        if(costWrap) costWrap.style.display = type==='add' ? '' : 'none';

        // Afficher le bon bouton Valider
        const vadd = document.getElementById('qaValAdd');
        const vrem = document.getElementById('qaValRem');
        if(vadd) vadd.style.display = type==='add' ? '' : 'none';
        if(vrem) vrem.style.display = type==='remove' ? '' : 'none';

        // Focus quantité
        setTimeout(()=>{ const q=document.getElementById('qaQty'); if(q) q.focus(); }, 60);
    },
    async _qaSubmit(pid, type){
        const wid = (document.getElementById('qaProdWh')||{}).value;
        const qty = parseFloat((document.getElementById('qaQty')||{}).value)||0;
        const unit = document.getElementById('qaUnit')?.value || 'piece';
        const lotNumber  = (document.getElementById('qaLot')?.value||'').trim();
        const expiryDate = document.getElementById('qaExp')?.value || '';
        const unitCost   = parseFloat(document.getElementById('qaUnitCost')?.value)||0;

        if(!wid)  { toast('Choisissez un magasin','error'); return; }
        if(qty<=0){ toast('Quantité > 0','error'); return; }

        // Validation traçabilité
        const ctx = this._qaCtx || {};
        if(ctx.needsLot && !lotNumber)  { toast('Numéro de lot obligatoire','error'); return; }
        if(ctx.needsExp && !expiryDate) { toast('Date de péremption obligatoire','error'); return; }

        try {
            await Api.mvAdd({
                productId:   parseInt(pid),
                warehouseId: parseInt(wid),
                type,
                quantity: qty,
                unit,
                lotNumber:   lotNumber  || null,
                expiryDate:  expiryDate || null,
                unitCost: type==='add' ? unitCost : 0,
                reason:'',
                site:     (document.getElementById('qaSite')||{}).value || '',
                operator: '',
                note:     (document.getElementById('qaNote')||{}).value || ''
            });
            toast(type==='add' ? 'Ajouté !' : 'Sortie enregistrée', 'success');
            Modal.close();
            this.load();
        } catch(e){ toast(e.message,'error'); }
    },


    _getSupplierLinkOptions(selectedId=''){
        return (this.suppliers || []).map(s => `<option value="${s.id}" ${String(selectedId)===String(s.id)?'selected':''}>${this._esc ? this._esc(s.name) : s.name}</option>`).join('');
    },

    _normalizeSupplierLinks(product){
        const raw = Array.isArray(product?.supplierLinks) ? product.supplierLinks : [];
        const rows = raw.map((row, idx) => ({
            _id: row._id || ('sl_' + idx + '_' + Date.now()),
            supplierId: String(row?.supplierId || ''),
            supplierSku: String(row?.supplierSku || ''),
            purchasePrice: Number(row?.purchasePrice || 0),
            packagingLabel: String(row?.packagingLabel || ''),
            packagingQty: Number(row?.packagingQty || 0),
            minOrderQty: Number(row?.minOrderQty || 0),
            leadTimeDays: Number(row?.leadTimeDays || 0),
            discountRate: Number(row?.discountRate || 0),
            preferred: !!row?.preferred,
            allowedWarehouses: Array.isArray(row?.allowedWarehouses) ? row.allowedWarehouses.map(String) : []
        })).filter(row => row.supplierId || row.supplierSku || row.purchasePrice > 0 || row.packagingLabel);

        if(!rows.length && product?.supplier){
            rows.push({
                _id: 'sl_legacy_' + Date.now(),
                supplierId: String(product.supplier || ''),
                supplierSku: '',
                purchasePrice: Number(product?.cost || product?.cump || 0),
                packagingLabel: String(product?.unitType || product?.packBaseUnit || ''),
                packagingQty: 1,
                minOrderQty: 0,
                leadTimeDays: 0,
                discountRate: 0,
                preferred: true,
                allowedWarehouses: Array.isArray(product?.allowedWarehouses) ? product.allowedWarehouses.map(String) : []
            });
        }
        return rows;
    },

    _supplierPackagingLabels(){
        return [
            { value:'', label:'— Unité de base —' },
            { value:'piece', label:'pièce(s)' },
            { value:'carton', label:'carton(s)' },
            { value:'palette', label:'palette(s)' },
            { value:'bidon', label:'bidon(s)' },
            { value:'fût', label:'fût(s)' },
            { value:'aérosol', label:'aérosol(s)' },
            { value:'sac', label:'sac(s)' },
            { value:'seau', label:'seau(x)' },
            { value:'rouleau', label:'rouleau(x)' },
            { value:'bobine', label:'bobine(s)' },
            { value:'tube', label:'tube(s)' },
            { value:'boîte', label:'boîte(s)' },
            { value:'paquet', label:'paquet(s)' },
        ];
    },

    _renderSupplierLinksEditor(product={}){
        const host = document.getElementById('pmSupplierLinksList');
        if(!host) return;
        const rows = this._normalizeSupplierLinks(product);
        host.innerHTML = '';
        if(!rows.length){
            host.innerHTML = `<div class="empty-state" style="padding:12px;border:1px dashed var(--border);border-radius:10px;color:var(--text-muted)">Aucune offre fournisseur configurée. Ajoutez au moins une offre pour remonter automatiquement le prix, la référence fournisseur et le conditionnement dans les bons de commande.</div>`;
            this._syncPreferredSupplierFromLinks();
            return;
        }
        const packagingOptions = this._supplierPackagingLabels().map(opt => `<option value="${opt.value}">${opt.label}</option>`).join('');
        const whOptions = (this.whs || []).map(w => `<option value="${w.id}">${this._esc ? this._esc(w.name) : w.name}</option>`).join('');
        rows.forEach((row, idx) => {
            const item = document.createElement('div');
            item.className = 'pm-supplier-link-row';
            item.dataset.rowId = row._id;
            item.style.cssText = 'border:1px solid var(--border);border-radius:12px;padding:12px;margin-bottom:10px;background:var(--bg-card)';
            item.innerHTML = `
                <div style="display:flex;justify-content:space-between;align-items:center;gap:10px;margin-bottom:10px;flex-wrap:wrap">
                    <strong>Offre fournisseur ${idx + 1}</strong>
                    <button type="button" class="btn btn-sm btn-ghost pm-sl-remove">Retirer</button>
                </div>
                <div style="display:grid;grid-template-columns:1.2fr 1fr 1fr;gap:10px">
                    <div>
                        <label class="lbl">Fournisseur</label>
                        <select class="sel pm-sl-supplier"><option value="">— Choisir —</option>${this._getSupplierLinkOptions(row.supplierId)}</select>
                    </div>
                    <div>
                        <label class="lbl">Réf. fournisseur</label>
                        <input type="text" class="inp pm-sl-sku" value="${this._esc ? this._esc(row.supplierSku) : row.supplierSku}" placeholder="Référence article">
                    </div>
                    <div>
                        <label class="lbl">Prix d'achat (€)</label>
                        <input type="number" class="inp pm-sl-price" min="0" step="0.01" value="${row.purchasePrice || ''}" placeholder="0.00">
                    </div>
                </div>
                <div style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:10px;margin-top:10px">
                    <div>
                        <label class="lbl">Conditionnement</label>
                        <select class="sel pm-sl-packaging">${packagingOptions}</select>
                    </div>
                    <div>
                        <label class="lbl">Qté / cond.</label>
                        <input type="number" class="inp pm-sl-packqty" min="0" step="0.01" value="${row.packagingQty || ''}" placeholder="1">
                    </div>
                    <div>
                        <label class="lbl">Qté mini</label>
                        <input type="number" class="inp pm-sl-minqty" min="0" step="0.01" value="${row.minOrderQty || ''}" placeholder="0">
                    </div>
                    <div>
                        <label class="lbl">Délai (jours)</label>
                        <input type="number" class="inp pm-sl-lead" min="0" step="1" value="${row.leadTimeDays || ''}" placeholder="0">
                    </div>
                </div>
                <div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px">
                    <div>
                        <label class="lbl">Remise (%)</label>
                        <input type="number" class="inp pm-sl-discount" min="0" step="0.01" value="${row.discountRate || ''}" placeholder="0">
                    </div>
                    <div>
                        <label class="lbl">Magasins autorisés (optionnel)</label>
                        <select class="sel pm-sl-warehouses" multiple size="3">${whOptions}</select>
                    </div>
                </div>
                <div style="display:flex;align-items:center;gap:8px;margin-top:10px">
                    <input type="radio" name="pmPreferredSupplierLink" class="pm-sl-preferred" ${row.preferred ? 'checked' : ''}>
                    <label style="margin:0">Fournisseur préféré pour les commandes</label>
                </div>
                <div class="pm-sl-summary" style="margin-top:10px;font-size:12px;color:var(--text-muted)"></div>
            `;
            host.appendChild(item);
            item.querySelector('.pm-sl-packaging').value = row.packagingLabel || '';
            const msel = item.querySelector('.pm-sl-warehouses');
            Array.from(msel.options).forEach(opt => { opt.selected = row.allowedWarehouses.includes(String(opt.value)); });
            item.querySelectorAll('input,select').forEach(el => {
                el.addEventListener('input', () => this._refreshSupplierLinkRow(item));
                el.addEventListener('change', () => this._refreshSupplierLinkRow(item));
            });
            item.querySelector('.pm-sl-remove')?.addEventListener('click', () => {
                item.remove();
                if(!host.children.length) this._renderSupplierLinksEditor({});
                else this._syncPreferredSupplierFromLinks();
            });
            this._refreshSupplierLinkRow(item);
        });
        this._syncPreferredSupplierFromLinks();
    },

    _refreshSupplierLinkRow(rowEl){
        if(!rowEl) return;
        const supplierId = rowEl.querySelector('.pm-sl-supplier')?.value || '';
        const sku = rowEl.querySelector('.pm-sl-sku')?.value?.trim() || '';
        const price = parseFloat(rowEl.querySelector('.pm-sl-price')?.value || 0) || 0;
        const packagingLabel = rowEl.querySelector('.pm-sl-packaging')?.value || '';
        const packagingQty = parseFloat(rowEl.querySelector('.pm-sl-packqty')?.value || 0) || 0;
        const minQty = parseFloat(rowEl.querySelector('.pm-sl-minqty')?.value || 0) || 0;
        const lead = parseInt(rowEl.querySelector('.pm-sl-lead')?.value || 0, 10) || 0;
        const supplier = (this.suppliers || []).find(s => String(s.id) === String(supplierId));
        const summary = [];
        if(supplier) summary.push(`<strong>${this._esc ? this._esc(supplier.name) : supplier.name}</strong>`);
        if(sku) summary.push(`réf. ${this._esc ? this._esc(sku) : sku}`);
        if(price > 0) summary.push(`${price.toFixed(2)}€ HT`);
        if(packagingLabel) summary.push(`conditionnement : 1 ${packagingLabel}${packagingQty > 0 ? ' = ' + packagingQty + ' unité(s)' : ''}`);
        if(minQty > 0) summary.push(`mini ${minQty}`);
        if(lead > 0) summary.push(`délai ${lead} j`);
        rowEl.querySelector('.pm-sl-summary').innerHTML = summary.length ? summary.join(' · ') : 'Complétez l’offre fournisseur.';
        this._syncPreferredSupplierFromLinks();
    },

    _syncPreferredSupplierFromLinks(){
        const rows = Array.from(document.querySelectorAll('#pmSupplierLinksList .pm-supplier-link-row'));
        if(!rows.length) return;
        let preferredRow = rows.find(row => row.querySelector('.pm-sl-preferred')?.checked) || rows[0];
        if(preferredRow && !preferredRow.querySelector('.pm-sl-preferred')?.checked){
            preferredRow.querySelector('.pm-sl-preferred').checked = true;
        }
        const mainSupplier = document.getElementById('pmSupp');
        if(mainSupplier){
            const supplierId = preferredRow.querySelector('.pm-sl-supplier')?.value || '';
            if(supplierId) mainSupplier.value = supplierId;
        }
    },

    _collectSupplierLinks(){
        const rows = Array.from(document.querySelectorAll('#pmSupplierLinksList .pm-supplier-link-row'));
        return rows.map(row => ({
            supplierId: row.querySelector('.pm-sl-supplier')?.value || '',
            supplierSku: row.querySelector('.pm-sl-sku')?.value?.trim() || '',
            purchasePrice: parseFloat(row.querySelector('.pm-sl-price')?.value || 0) || 0,
            packagingLabel: row.querySelector('.pm-sl-packaging')?.value || '',
            packagingQty: parseFloat(row.querySelector('.pm-sl-packqty')?.value || 0) || 0,
            minOrderQty: parseFloat(row.querySelector('.pm-sl-minqty')?.value || 0) || 0,
            leadTimeDays: parseInt(row.querySelector('.pm-sl-lead')?.value || 0, 10) || 0,
            discountRate: parseFloat(row.querySelector('.pm-sl-discount')?.value || 0) || 0,
            preferred: !!row.querySelector('.pm-sl-preferred')?.checked,
            allowedWarehouses: Array.from(row.querySelector('.pm-sl-warehouses')?.selectedOptions || []).map(opt => String(opt.value))
        })).filter(row => row.supplierId || row.supplierSku || row.purchasePrice > 0 || row.packagingLabel);
    },

    _appendEmptySupplierLink(){
        const current = { supplierLinks: this._collectSupplierLinks() };
        current.supplier = document.getElementById('pmSupp')?.value || '';
        current.supplierLinks.push({
            supplierId: current.supplier || '',
            supplierSku: '',
            purchasePrice: 0,
            packagingLabel: '',
            packagingQty: 1,
            minOrderQty: 0,
            leadTimeDays: 0,
            discountRate: 0,
            preferred: current.supplierLinks.length === 0,
            allowedWarehouses: []
        });
        this._renderSupplierLinksEditor(current);
    },

    async openModal(pid){
        const isAdmin = (window.SM_ROLE === 'admin');
        
        // ══════════════════════════════════════════════════════════════
        // MODE CONSULTATION (non-admin) - Affichage lecture seule
        // ══════════════════════════════════════════════════════════════
        if(!isAdmin && pid){
            return this._openViewModal(pid);
        }
        
        // ══════════════════════════════════════════════════════════════
        // MODE EDITION (admin) - Formulaire complet
        // ══════════════════════════════════════════════════════════════
        const whs = this.whs;
        const suppliers = this.suppliers;
        const whOpts = whs.map(w=>`<option value="${w.id}">${w.name}</option>`).join('');
        const catOpts = this.cats.map(c=>`<option value="${c}">${c}</option>`).join('');
        const suppOpts = suppliers.map(s=>`<option value="${s.id}">${s.name}</option>`).join('');
        let p = {};
        let currentStock = [];
        if(pid) { 
            const all = await Api.products(); 
            p = all.find(x=>String(x.id)===String(pid))||{}; 
            // Charger le stock actuel pour ce produit
            currentStock = (this.stock || []).filter(s => String(s.productId) === String(pid));
        }
        const isEdit = !!pid;
        const stockBaseUnit = this._getBaseUnit(p);
        const unitLabel = stockBaseUnit === 'piece' ? 'u' : stockBaseUnit;
        
        // Construire l'affichage du stock actuel
        let stockInfoHtml = '';
        if(isEdit && currentStock.length > 0) {
            const totalQty = currentStock.reduce((sum, s) => sum + (parseFloat(s.quantity) || 0), 0);
            const baseCost = getBaseCost(p);
            const totalValue = totalQty * baseCost;
            
            const stockLines = currentStock.map(s => {
                const wh = whs.find(w => String(w.id) === String(s.warehouseId));
                const qty = parseFloat(s.quantity) || 0;
                return `<div style="display:flex;justify-content:space-between;padding:4px 0;border-bottom:1px solid var(--border)">
                    <span>${wh ? wh.name : 'Magasin ?'}</span>
                    <strong>${qty} ${unitLabel}</strong>
                </div>`;
            }).join('');
            
            stockInfoHtml = `
                <div class="section-sep"></div>
                <h4 style="margin:8px 0 4px 0">📦 Stock actuel</h4>
                <div style="background:var(--bg-alt);border:1px solid var(--border);border-radius:8px;padding:12px;margin-bottom:12px">
                    ${stockLines}
                    <div style="display:flex;justify-content:space-between;padding:8px 0 0 0;margin-top:8px;border-top:2px solid var(--border)">
                        <strong>Total</strong>
                        <strong>${totalQty} ${unitLabel} · ${totalValue.toFixed(2)}€</strong>
                    </div>
                </div>
                <small style="color:var(--text-muted)">💡 Pour modifier le stock, utilisez les mouvements (entrée/sortie)</small>
            `;
        } else if(isEdit) {
            stockInfoHtml = `
                <div class="section-sep"></div>
                <h4 style="margin:8px 0 4px 0">📦 Stock actuel</h4>
                <div style="background:var(--bg-alt);border:1px solid var(--border);border-radius:8px;padding:12px;margin-bottom:12px;color:var(--text-muted)">
                    Aucun stock enregistré pour ce produit.
                </div>
            `;
        }
        
        Modal.show(isEdit?'Modifier produit':'Nouveau produit', `
            ${stockInfoHtml}
            
            <label class="lbl">Nom *</label>
            <input type="text" class="inp" id="pmName" value="${p.name||''}">
            
            <label class="lbl">Référence (SKU)</label>
            <div style="position:relative">
                <input type="text" class="inp" id="pmSKU" value="${p.sku||''}" placeholder="Auto-généré si vide" style="padding-right:36px">
                <span id="pmSKUStatus" style="position:absolute;right:10px;top:50%;transform:translateY(-50%);font-size:16px;display:none"></span>
            </div>
            <div id="pmSKUHint" style="font-size:11px;margin-top:3px;display:none"></div>
            
            <label class="lbl">Code-barres (EAN/QR)</label>
            <input type="text" class="inp" id="pmBarcode" value="${p.barcode||''}">
            
            <div class="section-sep"></div>
            <h4 style="margin:8px 0 4px 0">🏢 Classification</h4>
            
            <label class="lbl">Magasin *</label>
            <select class="sel" id="pmWarehouse">
                <option value="">— Choisir un magasin —</option>
                ${whOpts}
            </select><div class="section-sep"></div>
<h4 style="margin:8px 0 4px 0">📍 Emplacement stock</h4>
<div style="margin-bottom:8px">
    <label class="lbl">Emplacement enregistré</label>
    <select class="sel" id="pmLocationId">
        <option value="">— Choisir un emplacement —</option>
    </select>
    <div id="pmLocHint" style="font-size:11px;color:var(--text-muted);margin-top:4px">Choisissez d'abord un magasin pour filtrer les emplacements.</div>
</div>
<details style="margin-bottom:4px">
    <summary style="font-size:12px;color:var(--text-muted);cursor:pointer;user-select:none">Ou saisir manuellement (texte libre)</summary>
    <div class="grid2" style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:8px">
        <div>
            <label class="lbl">Pick'in / Bac</label>
            <input type="text" class="inp" id="pmPicking" value="">
        </div>
        <div>
            <label class="lbl">Lieu</label>
            <input type="text" class="inp" id="pmLocation" value="">
        </div>
        <div>
            <label class="lbl">Rack</label>
            <input type="text" class="inp" id="pmRack" value="">
        </div>
        <div>
            <label class="lbl">Étagère</label>
            <input type="text" class="inp" id="pmShelfLoc" value="">
        </div>
    </div>
</details>
            
            <label class="lbl">Catégorie</label>
            <select class="sel" id="pmCat">
                <option value="">— — Choisir d'abord un magasin — —</option>
            </select>
            
            <label class="lbl">Fournisseur principal</label>
            <select class="sel" id="pmSupp"><option value="">— Aucun —</option>${suppOpts}</select>
            <div style="font-size:12px;color:var(--text-muted);margin-top:4px">Conserve la compatibilité legacy. Les commandes utiliseront en priorité les offres détaillées ci-dessous.</div>
            <div class="section-sep"></div>
            <h4 style="margin:8px 0 4px 0">🤝 Offres fournisseurs par produit</h4>
            <div id="pmSupplierLinksList"></div>
            <div style="display:flex;justify-content:flex-end;margin:8px 0 4px 0">
                <button type="button" class="btn btn-secondary btn-sm" id="btnAddSupplierLink">+ Ajouter une offre fournisseur</button>
            </div>
            <div id="pmBrandContainer">
            </div>
            
            <div class="section-sep"></div>
            <h4 style="margin:8px 0 4px 0">💰 Prix</h4>
            
            <label class="lbl">Prix d'achat unitaire (€)</label>
            <input type="number" class="inp" id="pmCost" min="0" step="0.01" value="${p.cost||0}">
            
            <div class="section-sep"></div>
            <h4 style="margin:8px 0 4px 0">📦 Conditionnement & traçabilité</h4>
            
            <label class="lbl">Unité de base</label>
            <select class="sel" id="pmBaseUnit">
                <option value="piece">pièce</option>
                <option value="ml">mètre linéaire (ml)</option>
                <option value="litre">litre</option>
                <option value="kg">kg</option>
                <option value="m">m</option>
                <option value="m2">m²</option>
                <option value="m3">m³</option>
            </select>
            
            <div class="row" style="align-items:center;gap:10px;margin:8px 0 6px 0">
                <input type="checkbox" id="pmUsePack" style="width:16px;height:16px">
                <label for="pmUsePack" style="margin:0">Activer les conditionnements (carton, palette, bidon, fût, aérosol, sac, seau, rouleau, bobine, tube, boîte, paquet)</label>
            </div>
            
            <div id="pmPackFields">
                <label class="lbl">Qté par carton (en unité de base)</label>
                <input type="number" class="inp" id="pmCartonQty" min="0" step="1" value="${p.packCartonQty??p.unitsPerBox??0}">
                <label class="lbl">Qté par palette (en unité de base)</label>
                <input type="number" class="inp" id="pmPaletteQty" min="0" step="1" value="${p.packPaletteQty??0}">
                <label class="lbl">Contenance bidon (litres/kg)</label>
                <input type="number" class="inp" id="pmBidonQty" min="0" step="0.01" value="${p.packBidonQty??p.volume??0}">
                <label class="lbl">Contenance fût (litres/kg)</label>
                <input type="number" class="inp" id="pmFutQty" min="0" step="0.01" value="${p.packFutQty??0}">
                <label class="lbl">Contenance aérosol (en unité de base)</label>
                <input type="number" class="inp" id="pmAerosolQty" min="0" step="0.01" value="${p.packAerosolQty??0}">
                <label class="lbl">Qté par sac (en unité de base)</label>
                <input type="number" class="inp" id="pmSacQty" min="0" step="0.01" value="${p.packSacQty??0}">
                <label class="lbl">Qté par seau (en unité de base)</label>
                <input type="number" class="inp" id="pmSeauQty" min="0" step="0.01" value="${p.packSeauQty??0}">
                <label class="lbl">Qté par rouleau (en unité de base)</label>
                <input type="number" class="inp" id="pmRouleauQty" min="0" step="0.01" value="${p.packRouleauQty??0}">
                <label class="lbl">Qté par bobine (en unité de base)</label>
                <input type="number" class="inp" id="pmBobineQty" min="0" step="0.01" value="${p.packBobineQty??0}">
                <label class="lbl">Qté par tube (en unité de base)</label>
                <input type="number" class="inp" id="pmTubeQty" min="0" step="0.01" value="${p.packTubeQty??0}">
                <label class="lbl">Qté par boîte (en unité de base)</label>
                <input type="number" class="inp" id="pmBoiteQty" min="0" step="0.01" value="${p.packBoiteQty??0}">
                <label class="lbl">Qté par paquet (en unité de base)</label>
                <input type="number" class="inp" id="pmPaquetQty" min="0" step="0.01" value="${p.packPaquetQty??0}">
            </div>

            <label class="lbl">Stock de sécurité (en unité de base)</label>
            <input type="number" class="inp" id="pmSafety" min="0" step="1" value="${p.safetyStock??''}" placeholder="Optionnel">
            
            <label class="lbl">Traçabilité</label>
            <label class="chk"><input type="checkbox" id="pmTrackLot" ${p.trackLot?'checked':''}> Numéro de lot obligatoire</label>
            <label class="chk"><input type="checkbox" id="pmTrackExp" ${p.trackExpiry?'checked':''}> Date de péremption / DLUO</label>
            
            <label class="lbl">Durée de vie (jours) — optionnel</label>
            <input type="number" class="inp" id="pmShelf" min="0" step="1" value="${p.shelfLifeDays??''}" placeholder="Ex: 365">
            
            <label class="lbl">Fiche technique (URL)</label>
            <input type="url" class="inp" id="pmDatasheet" value="${p.datasheetUrl||''}" placeholder="https://...">
            
            <label class="lbl">Fiche de sécurité (URL)</label>
            <input type="url" class="inp" id="pmMsds" value="${p.msdsUrl||''}" placeholder="https://...">
            
            <div class="section-sep"></div>
            ${!isEdit ? `<h4 style="margin:8px 0 4px 0">📥 Stock initial</h4>
            
            <label class="lbl">Quantité initiale (optionnel, en unité de base)</label>
            <input type="number" class="inp" id="pmInitQty" min="0" step="0.01" value="0">
            
            <div id="pmInitTrace" style="display:none">
                <label class="lbl">Lot (si stock initial)</label>
                <input type="text" class="inp" id="pmInitLot" value="">
                <label class="lbl">DLUO/DLC (si stock initial)</label>
                <input type="date" class="inp" id="pmInitExp" value="">
            </div>` : ''}

            <label class="lbl">Description</label>
            <textarea class="inp" id="pmDesc" rows="3" style="resize:vertical" placeholder="Description du produit…">${p.description||''}</textarea>
            <label class="lbl">Image</label>
            <div id="pmImgZone" class="pm-img-zone ${p.image ? 'pm-img-zone--has-img' : ''}">
                <!-- Zone drag & drop / aperçu -->
                <div class="pm-img-preview" id="pmImgPreview">
                    ${p.image
                        ? `<img src="${p.image}" id="pmImgPreviewImg" alt="aperçu">`
                        : `<div class="pm-img-placeholder" id="pmImgPlaceholder">
                            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="40" height="40" opacity=".35">
                                <rect x="3" y="3" width="18" height="18" rx="3"/>
                                <circle cx="8.5" cy="8.5" r="1.5"/>
                                <path d="M21 15l-5-5L5 21"/>
                            </svg>
                            <span>Glissez ou collez une image ici</span>
                           </div>`
                    }
                </div>
                <!-- Actions -->
                <div class="pm-img-actions">
                    <label class="pm-img-btn pm-img-btn--primary" for="pmImg" title="Parcourir un fichier">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="15" height="15">
                            <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
                            <polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/>
                        </svg>
                        Parcourir
                    </label>
                    <input type="file" id="pmImg" accept="image/*" style="display:none">
                    <button type="button" class="pm-img-btn" id="pmImgUrlBtn" title="Coller une URL d'image">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="15" height="15">
                            <path d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71"/>
                            <path d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71"/>
                        </svg>
                        URL
                    </button>
                    ${p.image ? `<button type="button" class="pm-img-btn pm-img-btn--danger" id="pmImgClear" title="Supprimer l'image">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="15" height="15">
                            <polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/>
                            <path d="M10 11v6M14 11v6"/><path d="M9 6V4a1 1 0 011-1h4a1 1 0 011 1v2"/>
                        </svg>
                        Supprimer
                    </button>` : ''}
                </div>
                <!-- Champ URL (caché par défaut) -->
                <div class="pm-img-url-row" id="pmImgUrlRow" style="display:none">
                    <input type="url" class="inp" id="pmImgUrl" placeholder="https://exemple.com/image.png">
                    <button type="button" class="pm-img-btn pm-img-btn--primary" id="pmImgUrlOk">OK</button>
                    <button type="button" class="pm-img-btn" id="pmImgUrlCancel">✕</button>
                </div>
                <!-- Nom du fichier sélectionné -->
                <div class="pm-img-filename" id="pmImgFilename" style="display:none"></div>
            </div>
            ${p.datasheetUrl?'<a href="'+p.datasheetUrl+'" target="_blank" class="btn btn-sm btn-secondary" style="margin-top:8px">📄 Voir fiche technique</a>':''}
            ${p.msdsUrl?'<a href="'+p.msdsUrl+'" target="_blank" class="btn btn-sm btn-secondary" style="margin-top:4px">⚠️ Voir fiche de sécurité</a>':''}
        `, [
            {label:'Annuler', cls:'btn-secondary', onclick:()=>Modal.close()},
            {label:isEdit?'Sauvegarder':'Créer', cls:'btn-primary', onclick:()=>this.saveProduct(pid)},
            ...(isEdit ? [{label:'Supprimer', cls:'btn-danger', onclick:()=>this.delProduct(pid)}] : [])
        ], isEdit?'info':'success');

        // ── Icône personnalisée du modal produit ────────────────────
        const modalIconEl = document.getElementById('modalIcon');
        if(modalIconEl){
            modalIconEl.innerHTML = isEdit
                ? '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>'
                : '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>';
            const hdr = modalIconEl.parentElement;
            if(hdr){
                hdr.style.background = isEdit ? 'var(--blue-pale, #dbeafe)' : 'var(--green-pale, #dcfce7)';
                const svg = modalIconEl.querySelector('svg');
                if(svg) svg.style.stroke = isEdit ? 'var(--blue, #3b82f6)' : 'var(--green, #22c55e)';
            }
        }

        // Init uploader image pro
        this._initImageUploader(p.image || '');

        // ── Vérification SKU en temps réel ──────────────────────────
        const skuInput  = document.getElementById('pmSKU');
        const skuStatus = document.getElementById('pmSKUStatus');
        const skuHint   = document.getElementById('pmSKUHint');
        if(skuInput && skuStatus && skuHint){
            let _skuTimer = null;
            skuInput.addEventListener('input', () => {
                clearTimeout(_skuTimer);
                const val = skuInput.value.trim();
                if(!val){ skuStatus.style.display='none'; skuHint.style.display='none'; skuInput.style.borderColor=''; return; }
                _skuTimer = setTimeout(async () => {
                    try {
                        const allProds = await Api.products();
                        const existing = allProds.find(x => 
                            x.sku && x.sku.toLowerCase() === val.toLowerCase() && String(x.id) !== String(pid)
                        );
                        if(existing){
                            skuStatus.textContent = '⚠️';
                            skuStatus.style.display = '';
                            skuHint.innerHTML = `<span style="color:var(--red)">Ce SKU est déjà utilisé par <strong>${existing.name}</strong></span>`;
                            skuHint.style.display = 'block';
                            skuInput.style.borderColor = 'var(--red)';
                        } else {
                            skuStatus.textContent = '✓';
                            skuStatus.style.display = '';
                            skuHint.innerHTML = '<span style="color:var(--green)">SKU disponible</span>';
                            skuHint.style.display = 'block';
                            skuInput.style.borderColor = 'var(--green)';
                            // Reset après 2s
                            setTimeout(() => { 
                                if(skuInput.value.trim() === val){ 
                                    skuHint.style.display='none'; 
                                    skuInput.style.borderColor=''; 
                                    skuStatus.style.display='none'; 
                                }
                            }, 2000);
                        }
                    } catch(e){}
                }, 800);
            });
        }

        // Inject CSS uploader image (une seule fois)
        if(!document.getElementById('pm-img-css')){
            const s = document.createElement('style');
            s.id = 'pm-img-css';
            s.textContent = `
.pm-img-zone{border:2px dashed var(--border,#e2e8f0);border-radius:12px;overflow:hidden;background:var(--bg-elevated,#f8fafc);transition:border-color .2s,background .2s;cursor:default}
.pm-img-zone--drag{border-color:var(--blue,#3b82f6);background:var(--blue-pale,#eff6ff)}
.pm-img-zone--has-img{border-style:solid;border-color:var(--border,#e2e8f0)}
.pm-img-preview{width:100%;min-height:140px;display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden;background:var(--bg-elevated,#f8fafc)}
.pm-img-preview img{width:100%;max-height:220px;object-fit:contain;display:block;transition:opacity .2s}
.pm-img-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:32px 20px;color:var(--text-muted,#94a3b8);font-size:13px;text-align:center;user-select:none;width:100%;min-height:130px}
.pm-img-actions{display:flex;gap:6px;flex-wrap:wrap;padding:10px 12px;border-top:1px solid var(--border,#e2e8f0);background:var(--bg-card,#fff)}
.pm-img-btn{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border-radius:8px;font-size:13px;font-weight:500;border:1.5px solid var(--border,#e2e8f0);background:var(--bg-card,#fff);color:var(--text-primary,#1e293b);cursor:pointer;transition:all .15s;white-space:nowrap;line-height:1}
.pm-img-btn:hover{background:var(--bg-elevated,#f8fafc);border-color:var(--text-muted,#94a3b8)}
.pm-img-btn--primary{background:var(--blue,#3b82f6);border-color:var(--blue,#3b82f6);color:#fff}
.pm-img-btn--primary:hover{background:var(--blue-dark,#2563eb);border-color:var(--blue-dark,#2563eb)}
.pm-img-btn--danger{background:#fff;border-color:var(--danger,#ef4444);color:var(--danger,#ef4444)}
.pm-img-btn--danger:hover{background:var(--red-pale,#fee2e2)}
.pm-img-url-row{display:flex;gap:6px;align-items:center;padding:8px 12px;border-top:1px solid var(--border,#e2e8f0);background:var(--bg-card,#fff)}
.pm-img-url-row .inp{flex:1;margin:0;font-size:13px}
.pm-img-filename{font-size:11.5px;color:var(--text-muted,#64748b);padding:4px 12px 6px;font-family:monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%;background:var(--bg-card,#fff)}
`;
            document.head.appendChild(s);
        }

        if(p.category) document.getElementById('pmCat').value = p.category;
        
        // CASCADE: Warehouse → Categories
        const whSelect = document.getElementById('pmWarehouse');
        const catSelect = document.getElementById('pmCat');
        
        const updateCategoriesForWarehouse = async () => {
            const selectedWhId = parseInt(whSelect.value) || 0;
            
            if(!selectedWhId) {
                catSelect.innerHTML = '<option value="">— Choisir magasin d\'abord —</option>';
                return;
            }
            
            try {
                // Load categories from selected warehouse
                const data = await Api.warehouseCategories(selectedWhId);
                const categories = Array.isArray(data) ? data : (data?.categories || []);
                
                if(categories.length > 0) {
                    catSelect.innerHTML = '<option value="">— Aucune —</option>' + 
                        categories.map(c => `<option value="${c}">${c}</option>`).join('');
                    
                    // Restore current value if editing
                    if(p.category) catSelect.value = p.category;
                } else {
                    catSelect.innerHTML = '<option value="">— Aucune catégorie dans ce magasin —</option>';
                }
            } catch(e) {
                console.error('Error loading categories:', e);
                catSelect.innerHTML = '<option value="">— Erreur chargement —</option>';
            }
        };
        
        // Set initial values if editing
        if(isEdit && p.allowedWarehouses) {
            try {
                let allowed = p.allowedWarehouses;
                // Handle both array and JSON string
                if(typeof allowed === 'string') {
                    allowed = JSON.parse(allowed || '[]');
                }
                if(Array.isArray(allowed) && allowed.length > 0) {
                    // Set the first warehouse as selected
                    whSelect.value = allowed[0];
                    await updateCategoriesForWarehouse();

                    // Load stock location from selected warehouse (fail-soft)
                    try {
                        const whId = allowed[0];
                        if(whId){
                            const st = await Api.stock(pid, whId);
                            const row = (Array.isArray(st) ? st[0] : (st?.stock?.[0] || null)) || null;
                            if(row){
                                const elPick    = document.getElementById('pmPicking');
                                const elLoc     = document.getElementById('pmLocation');
                                const elRack    = document.getElementById('pmRack');
                                const elShelf   = document.getElementById('pmShelfLoc');
                                const elLocId   = document.getElementById('pmLocationId');
                                if(elPick)  elPick.value  = row.picking  || '';
                                if(elLoc)   elLoc.value   = row.location || '';
                                if(elRack)  elRack.value  = row.rack     || '';
                                if(elShelf) elShelf.value = row.shelf    || '';
                                // Restaurer l'emplacement lié si présent
                                if(elLocId && row.locationId) {
                                    // Charger les emplacements du magasin d'abord
                                    const whId = allowed[0];
                                    try {
                                        const locs = await Api.locations(whId);
                                        const active = locs.filter(l => l.active !== false);
                                        elLocId.innerHTML = '<option value="">— Aucun emplacement spécifique —</option>' +
                                            active.map(l => {
                                                const label = [l.code, l.label, l.zone].filter(Boolean).join(' · ');
                                                return `<option value="${l.id}" data-code="${l.code||''}" data-label="${l.label||''}" data-zone="${l.zone||''}">${label}</option>`;
                                            }).join('');
                                        elLocId.value = String(row.locationId);
                                        const locHint = document.getElementById('pmLocHint');
                                        if(locHint) locHint.textContent = active.length + ' emplacement(s) disponible(s) pour ce magasin.';
                                    } catch(e) { /* fail-soft */ }
                                }
                            }
                        }
                    } catch(e) { /* fail-soft */ }
                }
            } catch(e) {
                console.error('Error parsing allowedWarehouses:', e);
            }
        }
        
        whSelect.onchange = updateCategoriesForWarehouse;
        
        // ── Cascade magasin → emplacements ──────────────────────────────
        const locSelect = document.getElementById('pmLocationId');
        const locHint   = document.getElementById('pmLocHint');
        const updateLocationsForWarehouse = async () => {
            const wid = parseInt(whSelect.value) || 0;
            if(!locSelect) return;
            if(!wid) {
                locSelect.innerHTML = '<option value="">— Choisir un emplacement —</option>';
                if(locHint) locHint.textContent = 'Choisissez d\'abord un magasin pour filtrer les emplacements.';
                return;
            }
            try {
                const locs = await Api.locations(wid);
                const active = locs.filter(l => l.active !== false);
                if(active.length === 0) {
                    locSelect.innerHTML = '<option value="">— Aucun emplacement pour ce magasin —</option>';
                    if(locHint) locHint.textContent = 'Aucun emplacement créé pour ce magasin. Gérez-les dans Emplacements.';
                } else {
                    locSelect.innerHTML = '<option value="">— Aucun emplacement spécifique —</option>' +
                        active.map(l => {
                            const label = [l.code, l.label, l.zone].filter(Boolean).join(' · ');
                            return `<option value="${l.id}" data-code="${l.code||''}" data-label="${l.label||''}" data-zone="${l.zone||''}">${label}</option>`;
                        }).join('');
                    if(locHint) locHint.textContent = active.length + ' emplacement(s) disponible(s) pour ce magasin.';
                }
            } catch(e) {
                locSelect.innerHTML = '<option value="">— Erreur chargement —</option>';
            }
        };
        
        // Synchro : quand on choisit un emplacement enregistré, remplir les champs libres
        if(locSelect) {
            locSelect.onchange = () => {
                const opt = locSelect.options[locSelect.selectedIndex];
                if(!opt || !opt.value) return;
                const code  = opt.getAttribute('data-code')  || '';
                const label = opt.getAttribute('data-label') || '';
                const zone  = opt.getAttribute('data-zone')  || '';
                const elPick = document.getElementById('pmPicking');
                const elLoc  = document.getElementById('pmLocation');
                const elRack = document.getElementById('pmRack');
                // Remplir les champs libres avec les données de l'emplacement choisi
                if(elPick && !elPick.value) elPick.value = code;
                if(elLoc  && !elLoc.value)  elLoc.value  = label || zone || code;
                if(elRack && !elRack.value) elRack.value = zone;
            };
        }
        
        // Déclencher le chargement au changement de magasin
        const origOnchange = whSelect.onchange;
        whSelect.onchange = async () => {
            await updateCategoriesForWarehouse();
            await updateLocationsForWarehouse();
        };
        // Chargement initial si magasin déjà sélectionné
        if(whSelect.value) await updateLocationsForWarehouse();
        
        // Brand cascade logic
        const updateBrandField = async () => {
            const suppSelect = document.getElementById('pmSupp');
            const suppId = parseInt(suppSelect?.value || '0', 10);
            const container = document.getElementById('pmBrandWrap')
                || document.getElementById('pmBrandContainer')
                || (document.getElementById('pmBrand') ? document.getElementById('pmBrand').parentElement : null);
            if (!container) return;

            // Always use a select (as requested). If supplier has no linked brands, show all brands.
            const allBrands = await Api.brands();
            const supplier = (this.suppliers || []).find(s => s.id === suppId);

            let linkedBrandIds = [];
            const raw = supplier?.linkedBrands;
            if (Array.isArray(raw)) {
                linkedBrandIds = raw.map(x => parseInt(x, 10)).filter(n => Number.isFinite(n));
            } else if (typeof raw === 'string' && raw.trim()) {
                try {
                    const parsed = JSON.parse(raw);
                    if (Array.isArray(parsed)) linkedBrandIds = parsed.map(x => parseInt(x, 10)).filter(n => Number.isFinite(n));
                } catch (e) { /* ignore */ }
            }
            const brandsToShow = linkedBrandIds.length
                ? allBrands.filter(b => linkedBrandIds.includes(b.id))
                : allBrands;

            const currentBrand = (document.getElementById('pmBrand')?.value || '').trim();

            const h = (v) => String(v ?? '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');

            // Build select + optional "Autre" input.
            container.innerHTML = `
                <label class="lbl">Marque</label>
                <select id="pmBrand" class="sel">
                    <option value="">— Aucune —</option>
                    ${brandsToShow.map(b => `<option value="${h(b.name)}">${h(b.name)}</option>`).join('')}
                    <option value="__other__">Autre (saisie libre)…</option>
                </select>
                <input id="pmBrandOther" class="inp" style="display:none;margin-top:8px" placeholder="Saisir une marque…">
            `;

            const brandSel = document.getElementById('pmBrand');
            const brandOther = document.getElementById('pmBrandOther');

            const applyValue = (val) => {
                const exists = [...brandSel.options].some(o => o.value === val);
                if (val && exists) {
                    brandSel.value = val;
                    brandOther.style.display = 'none';
                    brandOther.value = '';
                } else if (val) {
                    brandSel.value = '__other__';
                    brandOther.style.display = '';
                    brandOther.value = val;
                } else {
                    brandSel.value = '';
                    brandOther.style.display = 'none';
                    brandOther.value = '';
                }
            };

            brandSel.addEventListener('change', () => {
                if (brandSel.value === '__other__') {
                    brandOther.style.display = '';
                    brandOther.focus();
                } else {
                    brandOther.style.display = 'none';
                    brandOther.value = '';
                }
            });

            applyValue(currentBrand);
        };
                    
                    // Set supplier if editing
        if(p.supplier) {
            document.getElementById('pmSupp').value = p.supplier;
        }
        
        // Listen to supplier changes
        document.getElementById('pmSupp').onchange = updateBrandField;
        
        // ALWAYS trigger cascade on load
        await updateBrandField();
        
        // Defaults for packaging
        const baseUnit = (p.packBaseUnit || (p.unitType==='liter'?'litre':p.unitType) || 'piece');
        if(document.getElementById('pmBaseUnit')) document.getElementById('pmBaseUnit').value = baseUnit;
        // Toggle packaging fields
        const allPackIds = ['pmCartonQty','pmPaletteQty','pmBidonQty','pmFutQty','pmAerosolQty','pmSacQty','pmSeauQty','pmRouleauQty','pmBobineQty','pmTubeQty','pmBoiteQty','pmPaquetQty'];
        const hasPack = allPackIds.some(id => {
            const key = id.replace('pm','pack');
            return (parseFloat(p[key] || p[key.charAt(0).toLowerCase()+key.slice(1)] || 0) > 0);
        }) || (parseFloat(p.packCartonQty||p.unitsPerBox||0) > 0);
        const usePack = document.getElementById('pmUsePack');
        const packBox = document.getElementById('pmPackFields');
        const togglePack = ()=>{
            const on = !!(usePack && usePack.checked);
            if(packBox) packBox.style.display = on ? 'block' : 'none';
            allPackIds.forEach(id=>{
                const el = document.getElementById(id);
                if(el) el.disabled = !on;
                if(el && !on) el.value = '0';
            });
        };
        if(usePack){
            usePack.checked = hasPack;
            usePack.onchange = togglePack;
        }
        togglePack();

        // Toggle initial trace inputs based on tracking flags
        const initTrace = document.getElementById('pmInitTrace');
        const trLot = document.getElementById('pmTrackLot');
        const trExp = document.getElementById('pmTrackExp');
        const toggleInitTrace = ()=>{
            const on = (!!(trLot && trLot.checked)) || (!!(trExp && trExp.checked));
            if(initTrace) initTrace.style.display = on ? 'block' : 'none';
        };
        if(trLot) trLot.onchange = toggleInitTrace;
        if(trExp) trExp.onchange = toggleInitTrace;
        toggleInitTrace();

    },
    
    /**
     * Modal de consultation en lecture seule (pour utilisateurs non-admin)
     */
    async _openViewModal(pid){
        const whs = this.whs;
        const all = await Api.products();
        const p = all.find(x => String(x.id) === String(pid)) || {};
        const currentStock = (this.stock || []).filter(s => String(s.productId) === String(pid));
        
        if(!p.id){
            toast('Produit introuvable', 'error');
            return;
        }
        
        const baseUnit = this._getBaseUnit(p);
        const unitLabel = baseUnit === 'piece' ? 'u' : baseUnit;
        const baseCost = getBaseCost(p);
        
        // Construire l'affichage du stock
        let stockHtml = '';
        if(currentStock.length > 0) {
            const totalQty = currentStock.reduce((sum, s) => sum + (parseFloat(s.quantity) || 0), 0);
            const totalValue = totalQty * baseCost;
            
            const stockLines = currentStock.map(s => {
                const wh = whs.find(w => String(w.id) === String(s.warehouseId));
                const qty = parseFloat(s.quantity) || 0;
                const minStock = s.minStock != null ? parseInt(s.minStock) : null;
                const maxStock = s.maxStock != null ? parseInt(s.maxStock) : null;
                
                let statusBadge = '<span class="badge badge-success">OK</span>';
                if(minStock != null && qty <= minStock) {
                    statusBadge = '<span class="badge badge-danger">Stock bas</span>';
                } else if(minStock != null && qty <= minStock * 1.5) {
                    statusBadge = '<span class="badge badge-warning">Attention</span>';
                }
                
                return `
                    <div style="display:flex;justify-content:space-between;align-items:center;padding:10px 0;border-bottom:1px solid var(--border-light)">
                        <div>
                            <strong>${wh ? wh.name : 'Magasin ?'}</strong>
                            ${s.location ? '<br><small style="color:var(--text-muted)">📍 ' + s.location + '</small>' : ''}
                        </div>
                        <div style="text-align:right">
                            <strong style="font-size:18px">${qty} ${unitLabel}</strong>
                            ${minStock != null ? '<br><small style="color:var(--text-muted)">Min: ' + minStock + '</small>' : ''}
                        </div>
                        <div style="margin-left:10px">${statusBadge}</div>
                    </div>`;
            }).join('');
            
            stockHtml = `
                <div style="background:var(--bg-elevated);border:1px solid var(--border);border-radius:var(--r-lg);padding:16px;margin-bottom:16px">
                    <h4 style="margin:0 0 12px 0;display:flex;align-items:center;gap:8px">
                        📦 Stock par magasin
                    </h4>
                    ${stockLines}
                    <div style="display:flex;justify-content:space-between;padding:12px 0 0 0;margin-top:8px;border-top:2px solid var(--border);font-size:16px">
                        <strong>Total</strong>
                        <strong>${totalQty} ${unitLabel} · ${totalValue.toFixed(2)}€</strong>
                    </div>
                </div>`;
        } else {
            stockHtml = `
                <div style="background:var(--bg-elevated);border:1px solid var(--border);border-radius:var(--r-lg);padding:20px;margin-bottom:16px;text-align:center;color:var(--text-muted)">
                    <svg viewBox="0 0 24 24" width="48" height="48" fill="none" stroke="currentColor" stroke-width="1.5" style="opacity:0.5;margin-bottom:8px">
                        <path d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z"/>
                    </svg>
                    <p>Aucun stock enregistré</p>
                </div>`;
        }
        
        // Informations produit
        const supplier = this.suppliers.find(s => String(s.id) === String(p.supplier));
        
        const infoHtml = `
            <div style="display:grid;grid-template-columns:repeat(2,1fr);gap:12px;margin-bottom:16px">
                <div style="background:var(--bg-elevated);padding:12px;border-radius:var(--r)">
                    <small style="color:var(--text-muted)">SKU</small>
                    <div style="font-weight:600;font-family:var(--font-mono)">${p.sku || '—'}</div>
                </div>
                <div style="background:var(--bg-elevated);padding:12px;border-radius:var(--r)">
                    <small style="color:var(--text-muted)">Code-barres</small>
                    <div style="font-weight:600;font-family:var(--font-mono)">${p.barcode || '—'}</div>
                </div>
                <div style="background:var(--bg-elevated);padding:12px;border-radius:var(--r)">
                    <small style="color:var(--text-muted)">Catégorie</small>
                    <div style="font-weight:600">${p.category || '—'}</div>
                </div>
                <div style="background:var(--bg-elevated);padding:12px;border-radius:var(--r)">
                    <small style="color:var(--text-muted)">Marque</small>
                    <div style="font-weight:600">${p.brand || '—'}</div>
                </div>
                ${supplier ? `
                <div style="background:var(--bg-elevated);padding:12px;border-radius:var(--r);grid-column:span 2">
                    <small style="color:var(--text-muted)">Fournisseur</small>
                    <div style="font-weight:600">${supplier.name}</div>
                </div>` : ''}
            </div>`;
        
        // Documents
        let docsHtml = '';
        if(p.datasheetUrl || p.msdsUrl){
            docsHtml = `
                <div style="display:flex;gap:8px;margin-top:16px;padding-top:16px;border-top:1px solid var(--border)">
                    ${p.datasheetUrl ? `<a href="${p.datasheetUrl}" target="_blank" class="btn btn-secondary btn-sm">📄 Fiche technique</a>` : ''}
                    ${p.msdsUrl ? `<a href="${p.msdsUrl}" target="_blank" class="btn btn-secondary btn-sm">⚠️ Fiche de sécurité</a>` : ''}
                </div>`;
        }
        
        // Image
        const imgHtml = p.image 
            ? `<div style="text-align:center;margin-bottom:16px"><img src="${p.image}" style="max-width:200px;max-height:150px;border-radius:var(--r-lg);object-fit:cover"></div>`
            : '';
        
        Modal.show(p.name, `
            ${imgHtml}
            ${stockHtml}
            ${infoHtml}
            ${docsHtml}
        `, [
            {label: 'Fermer', cls: 'btn-secondary', onclick: () => Modal.close()}
        ], 'info');

        // ── Icône personnalisée du modal consultation ────────────────
        const mIcon = document.getElementById('modalIcon');
        if(mIcon){
            mIcon.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>';
            const hdr = mIcon.parentElement;
            if(hdr) hdr.style.background = 'var(--blue-pale, #dbeafe)';
            const svg = mIcon.querySelector('svg');
            if(svg) svg.style.stroke = 'var(--blue, #3b82f6)';
        }
    },
    
    async _syncStockLocations(productId, warehouseIds){
    const pid = parseInt(productId||0);
    if(!pid) return;
    const whs = Array.isArray(warehouseIds) ? warehouseIds.map(x=>parseInt(x)).filter(Boolean) : [];
    if(whs.length===0) return;

    const picking    = (document.getElementById('pmPicking')?.value || '').trim();
    const location   = (document.getElementById('pmLocation')?.value || '').trim();
    const rack       = (document.getElementById('pmRack')?.value || '').trim();
    const shelf      = (document.getElementById('pmShelfLoc')?.value || '').trim();
    const locSel     = document.getElementById('pmLocationId');
    const locationId = locSel?.value ? parseInt(locSel.value) : null;
    // Si un emplacement enregistré est sélectionné, on utilise son code comme "location" (rétrocompat)
    const locOpt     = locSel ? locSel.options[locSel.selectedIndex] : null;
    const locCode    = (locationId && locOpt) ? (locOpt.getAttribute('data-code') || '') : '';

    // Upsert stock row for each selected warehouse (creates assignment effectively)
    await Promise.all(whs.map(wid => Api.stockSet({
        productId: pid,
        warehouseId: wid,
        picking,
        location:   location || locCode,
        rack,
        shelf,
        locationId: locationId || null
    })));
},

    // ─────────────────────────────────────────────────────────────
    // Uploader image pro : fichier + drag&drop + URL
    // ─────────────────────────────────────────────────────────────
    _initImageUploader(currentImageUrl){
        // État interne
        this._imgUploadFile = null;
        this._imgUploadUrl  = currentImageUrl || '';

        const zone     = document.getElementById('pmImgZone');
        const fileInp  = document.getElementById('pmImg');
        const preview  = document.getElementById('pmImgPreview');
        const urlBtn   = document.getElementById('pmImgUrlBtn');
        const urlRow   = document.getElementById('pmImgUrlRow');
        const urlInp   = document.getElementById('pmImgUrl');
        const urlOk    = document.getElementById('pmImgUrlOk');
        const urlCancel= document.getElementById('pmImgUrlCancel');
        const clearBtn = document.getElementById('pmImgClear');
        const filename = document.getElementById('pmImgFilename');

        if(!zone || !fileInp) return;

        const _showPreview = (src, label) => {
            preview.innerHTML = `<img src="${src}" id="pmImgPreviewImg" alt="aperçu" onerror="this.parentElement.innerHTML='<span style=color:var(--danger)>Image invalide</span>'">`;
            zone.classList.add('pm-img-zone--has-img');
            if(filename && label){
                filename.textContent = label;
                filename.style.display = 'block';
            }
            // Montrer bouton supprimer
            if(!document.getElementById('pmImgClear')){
                const del = document.createElement('button');
                del.type = 'button';
                del.id = 'pmImgClear';
                del.className = 'pm-img-btn pm-img-btn--danger';
                del.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="15" height="15"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/><path d="M10 11v6M14 11v6"/><path d="M9 6V4a1 1 0 011-1h4a1 1 0 011 1v2"/></svg> Supprimer`;
                del.onclick = _clearImg;
                document.querySelector('#pmImgZone .pm-img-actions')?.appendChild(del);
            }
        };

        const _clearImg = () => {
            this._imgUploadFile = null;
            this._imgUploadUrl  = '';
            preview.innerHTML = `<div class="pm-img-placeholder" id="pmImgPlaceholder">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="40" height="40" opacity=".35">
                    <rect x="3" y="3" width="18" height="18" rx="3"/>
                    <circle cx="8.5" cy="8.5" r="1.5"/>
                    <path d="M21 15l-5-5L5 21"/>
                </svg>
                <span>Glissez ou collez une image ici</span>
            </div>`;
            zone.classList.remove('pm-img-zone--has-img');
            fileInp.value = '';
            document.getElementById('pmImgClear')?.remove();
            if(filename){ filename.textContent=''; filename.style.display='none'; }
        };

        // ── Fichier via input ────────────────────────────────────
        fileInp.onchange = () => {
            const f = fileInp.files[0];
            if(!f) return;
            this._imgUploadFile = f;
            this._imgUploadUrl  = '';
            const reader = new FileReader();
            reader.onload = e => _showPreview(e.target.result, f.name);
            reader.readAsDataURL(f);
        };

        // ── Drag & Drop ──────────────────────────────────────────
        zone.addEventListener('dragover', e => {
            e.preventDefault();
            zone.classList.add('pm-img-zone--drag');
        });
        zone.addEventListener('dragleave', () => zone.classList.remove('pm-img-zone--drag'));
        zone.addEventListener('drop', e => {
            e.preventDefault();
            zone.classList.remove('pm-img-zone--drag');
            const f = e.dataTransfer?.files?.[0];
            if(f && f.type.startsWith('image/')){
                this._imgUploadFile = f;
                this._imgUploadUrl  = '';
                const reader = new FileReader();
                reader.onload = ev => _showPreview(ev.target.result, f.name);
                reader.readAsDataURL(f);
            }
        });

        // ── Coller une image (Ctrl+V / Cmd+V) ───────────────────
        // Écoute globale sur le document mais ne réagit que si le modal produit est ouvert
        if(!window._pmPasteListenerBound){
            window._pmPasteListenerBound = true;
            document.addEventListener('paste', (e) => {
                // Ne réagir que si la zone d'upload existe dans le DOM (= modal ouvert)
                const z = document.getElementById('pmImgZone');
                if(!z) return;
                const items = e.clipboardData?.items;
                if(!items) return;
                for(const item of items){
                    if(item.type.startsWith('image/')){
                        e.preventDefault();
                        const f = item.getAsFile();
                        if(!f) continue;
                        ProdPage._imgUploadFile = f;
                        ProdPage._imgUploadUrl  = '';
                        const reader = new FileReader();
                        reader.onload = ev => {
                            const preview = document.getElementById('pmImgPreview');
                            const zone = document.getElementById('pmImgZone');
                            const filename = document.getElementById('pmImgFilename');
                            if(preview){
                                preview.innerHTML = `<img src="${ev.target.result}" id="pmImgPreviewImg" alt="aperçu">`;
                            }
                            if(zone) zone.classList.add('pm-img-zone--has-img');
                            if(filename){ filename.textContent = 'Image collée'; filename.style.display = 'block'; }
                            // Ajouter bouton supprimer si absent
                            if(!document.getElementById('pmImgClear')){
                                const del = document.createElement('button');
                                del.type = 'button'; del.id = 'pmImgClear';
                                del.className = 'pm-img-btn pm-img-btn--danger';
                                del.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="15" height="15"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/><path d="M10 11v6M14 11v6"/><path d="M9 6V4a1 1 0 011-1h4a1 1 0 011 1v2"/></svg> Supprimer`;
                                del.onclick = () => {
                                    ProdPage._imgUploadFile = null;
                                    ProdPage._imgUploadUrl = '';
                                    const p2 = document.getElementById('pmImgPreview');
                                    if(p2) p2.innerHTML = `<div class="pm-img-placeholder"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="40" height="40" opacity=".35"><rect x="3" y="3" width="18" height="18" rx="3"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg><span>Glissez ou collez une image ici</span></div>`;
                                    const z2 = document.getElementById('pmImgZone');
                                    if(z2) z2.classList.remove('pm-img-zone--has-img');
                                    const fi = document.getElementById('pmImg');
                                    if(fi) fi.value = '';
                                    del.remove();
                                    const fn = document.getElementById('pmImgFilename');
                                    if(fn){ fn.textContent=''; fn.style.display='none'; }
                                };
                                document.querySelector('#pmImgZone .pm-img-actions')?.appendChild(del);
                            }
                        };
                        reader.readAsDataURL(f);
                        break;
                    }
                }
            });
        }

        // ── Bouton URL ───────────────────────────────────────────
        if(urlBtn) urlBtn.onclick = () => {
            urlRow.style.display = 'flex';
            urlInp.value = this._imgUploadUrl || '';
            urlInp.focus();
        };
        if(urlCancel) urlCancel.onclick = () => { urlRow.style.display = 'none'; };
        if(urlOk) urlOk.onclick = () => {
            const url = urlInp.value.trim();
            if(!url){ urlRow.style.display = 'none'; return; }
            this._imgUploadFile = null;
            this._imgUploadUrl  = url;
            _showPreview(url, url.split('/').pop());
            urlRow.style.display = 'none';
        };
        // Valider URL sur Entrée
        if(urlInp) urlInp.onkeydown = e => { if(e.key==='Enter'){ e.preventDefault(); urlOk?.click(); } };

        // ── Supprimer image existante ────────────────────────────
        if(clearBtn) clearBtn.onclick = _clearImg;
    },

    async saveProduct(pid){
        const name = document.getElementById('pmName').value.trim();
        if(!name){toast('Nom obligatoire','error');return;}

        // Get selected warehouse (single select)
        const whSelect = document.getElementById('pmWarehouse');
        const selectedWarehouseId = parseInt(whSelect.value) || 0;
        
        if(!selectedWarehouseId) {
            toast('Veuillez sélectionner un magasin', 'error');
            return;
        }
        
        // Create array with single warehouse
        const allowedWarehouses = [selectedWarehouseId];
        
        // ================================================================
        // DEMANDE #2: AUTO-CRÉATION MARQUE
        // ================================================================
        let brandValue = '';
        const brandEl = document.getElementById('pmBrand');
        const supplierId = parseInt(document.getElementById('pmSupp').value) || null;
        
        if(brandEl) {
            if(brandEl.tagName === 'SELECT' && brandEl.value === '__other__') {
                // User selected "Autre" - get custom brand name
                const otherInput = document.getElementById('pmBrandOther');
                brandValue = otherInput ? otherInput.value.trim() : '';
                
                // Auto-create brand if new and supplier selected
                if(brandValue && supplierId) {
                    try {
                        // Check if brand already exists globally
                        const allBrands = await Api.brands();
                        const existingBrand = allBrands.find(b => 
                            b.name.toLowerCase() === brandValue.toLowerCase()
                        );
                        
                        if(!existingBrand) {
                            // Create new brand
                            const newBrand = await Api.brandAdd({ name: brandValue });
                            
                            // Link brand to supplier
                            const supplier = this.suppliers.find(s => parseInt(s.id) === supplierId);
                            if(supplier) {
                                let linkedBrands = [];
                                try { const v = supplier.linkedBrands; linkedBrands = Array.isArray(v) ? v : JSON.parse(v || '[]'); } catch(e) { linkedBrands = []; }
                                linkedBrands = (Array.isArray(linkedBrands)?linkedBrands:[]).map(x=>parseInt(x)).filter(x=>Number.isFinite(x));
                                if(!linkedBrands.includes(parseInt(newBrand.id))) {
                                    linkedBrands.push(parseInt(newBrand.id));
                                    await Api.supplierEdit({
                                        id: supplierId,
                                        linkedBrands: JSON.stringify(linkedBrands)
                                    });
                                }
                            }
                            
                            toast(`✅ Marque "${brandValue}" créée et liée au fournisseur`, 'success');
                        } else {
                            // Brand exists - link to supplier if not already
                            const supplier = this.suppliers.find(s => parseInt(s.id) === supplierId);
                            if(supplier) {
                                let linkedBrands = [];
                                try { const v = supplier.linkedBrands; linkedBrands = Array.isArray(v) ? v : JSON.parse(v || '[]'); } catch(e) { linkedBrands = []; }
                                linkedBrands = (Array.isArray(linkedBrands)?linkedBrands:[]).map(x=>parseInt(x)).filter(x=>Number.isFinite(x));
                                if(!linkedBrands.includes(parseInt(existingBrand.id))) {
                                    linkedBrands.push(parseInt(existingBrand.id));
                                    await Api.supplierEdit({
                                        id: supplierId,
                                        linkedBrands: JSON.stringify(linkedBrands)
                                    });
                                    toast(`✅ Marque "${brandValue}" liée au fournisseur`, 'info');
                                }
                            }
                        }
                    } catch(e) {
                        console.error('[Products] Error auto-creating brand:', e);
                        // Continue anyway - don't block product creation
                    }
                }
            } else {
                brandValue = brandEl.value.trim();
            }
        }
        
        const imgFile    = this._imgUploadFile || null;
        const imgUrlOnly = (!imgFile && this._imgUploadUrl) ? this._imgUploadUrl : null;
        const packEnabled = !!(document.getElementById('pmUsePack') && document.getElementById('pmUsePack').checked);
        const baseUnit = document.getElementById('pmBaseUnit').value;
        const packCartonQty = packEnabled ? (parseInt(document.getElementById('pmCartonQty').value)||0) : 0;
        const packPaletteQty = packEnabled ? (parseInt(document.getElementById('pmPaletteQty').value)||0) : 0;
        const packBidonQty = packEnabled ? (parseFloat(document.getElementById('pmBidonQty').value)||0) : 0;
        const packFutQty = packEnabled ? (parseFloat(document.getElementById('pmFutQty').value)||0) : 0;
        const packAerosolQty = packEnabled ? (parseFloat(document.getElementById('pmAerosolQty')?.value)||0) : 0;
        const packSacQty = packEnabled ? (parseFloat(document.getElementById('pmSacQty')?.value)||0) : 0;
        const packSeauQty = packEnabled ? (parseFloat(document.getElementById('pmSeauQty')?.value)||0) : 0;
        const packRouleauQty = packEnabled ? (parseFloat(document.getElementById('pmRouleauQty')?.value)||0) : 0;
        const packBobineQty = packEnabled ? (parseFloat(document.getElementById('pmBobineQty')?.value)||0) : 0;
        const packTubeQty = packEnabled ? (parseFloat(document.getElementById('pmTubeQty')?.value)||0) : 0;
        const packBoiteQty = packEnabled ? (parseFloat(document.getElementById('pmBoiteQty')?.value)||0) : 0;
        const packPaquetQty = packEnabled ? (parseFloat(document.getElementById('pmPaquetQty')?.value)||0) : 0;

        const initQty = (!pid) ? (parseFloat(document.getElementById('pmInitQty')?.value)||0) : 0;
        const initLot = (!pid) ? (document.getElementById('pmInitLot')?.value||'').trim() : '';
        const initExp = (!pid) ? (document.getElementById('pmInitExp')?.value||'').trim() : '';
        const supplierLinks = this._collectSupplierLinks();

        try {
            if(imgFile && !pid){
                const fd = new FormData();
                fd.append('name', name);
                fd.append('sku', document.getElementById('pmSKU').value);
                fd.append('barcode', document.getElementById('pmBarcode').value);
                fd.append('brand', brandValue);
                fd.append('category', document.getElementById('pmCat').value);
                fd.append('description', document.getElementById('pmDesc').value);
                fd.append('cost', document.getElementById('pmCost').value);
                fd.append('supplier', document.getElementById('pmSupp').value);
                fd.append('supplierLinks', JSON.stringify(supplierLinks));
                fd.append('allowedWarehouses', JSON.stringify(allowedWarehouses));
                fd.append('defaultWarehouse', String(selectedWarehouseId));
                fd.append('packBaseUnit', baseUnit);
                fd.append('unitType', baseUnit);
                fd.append('packCartonQty', String(packCartonQty));
                fd.append('packPaletteQty', String(packPaletteQty));
                fd.append('packBidonQty', String(packBidonQty));
                fd.append('packFutQty', String(packFutQty));
                fd.append('packAerosolQty', String(packAerosolQty));
                fd.append('packSacQty', String(packSacQty));
                fd.append('packSeauQty', String(packSeauQty));
                fd.append('packRouleauQty', String(packRouleauQty));
                fd.append('packBobineQty', String(packBobineQty));
                fd.append('packTubeQty', String(packTubeQty));
                fd.append('packBoiteQty', String(packBoiteQty));
                fd.append('packPaquetQty', String(packPaquetQty));
                fd.append('safetyStock', document.getElementById('pmSafety').value);
                fd.append('trackLot', document.getElementById('pmTrackLot').checked);
                fd.append('trackExpiry', document.getElementById('pmTrackExp').checked);
                fd.append('shelfLifeDays', document.getElementById('pmShelf').value);
                fd.append('datasheetUrl', document.getElementById('pmDatasheet').value);
                fd.append('msdsUrl', document.getElementById('pmMsds').value);
                fd.append('image', imgFile);
                const created = await Api.prodAddImg(fd);
                await this._maybeInitStock(created?.id, initQty, baseUnit, initLot, initExp);
                await this._syncStockLocations(created?.id, allowedWarehouses);
            } else if(!pid){
                const created = await Api.prodAdd({
                    name, sku:document.getElementById('pmSKU').value, barcode:document.getElementById('pmBarcode').value,
                    brand:brandValue, category:document.getElementById('pmCat').value,
                    description:document.getElementById('pmDesc').value, cost:parseFloat(document.getElementById('pmCost').value)||0,
                    supplier:document.getElementById('pmSupp').value,
                    supplierLinks:supplierLinks,
                    supplierLinks:supplierLinks,
                    allowedWarehouses:JSON.stringify(allowedWarehouses),
                    defaultWarehouse:selectedWarehouseId,
                    unitType:baseUnit,
                    packBaseUnit:baseUnit,
                    packCartonQty:packCartonQty,
                    packPaletteQty:packPaletteQty,
                    packBidonQty:packBidonQty,
                    packFutQty:packFutQty,
                    packPaquetQty:packPaquetQty,
                    packBoiteQty:packBoiteQty,
                    packTubeQty:packTubeQty,
                    packBobineQty:packBobineQty,
                    packRouleauQty:packRouleauQty,
                    packSeauQty:packSeauQty,
                    packSacQty:packSacQty,
                    packAerosolQty:packAerosolQty,
                    safetyStock:document.getElementById('pmSafety').value===''?null:parseInt(document.getElementById('pmSafety').value),
                    trackLot:document.getElementById('pmTrackLot').checked,
                    trackExpiry:document.getElementById('pmTrackExp').checked,
                    shelfLifeDays:document.getElementById('pmShelf').value===''?null:parseInt(document.getElementById('pmShelf').value),
                    datasheetUrl:document.getElementById('pmDatasheet').value,
                    msdsUrl:document.getElementById('pmMsds').value,
                    image: imgUrlOnly || ''
                });
                await this._maybeInitStock(created?.id, initQty, baseUnit, initLot, initExp);
                await this._syncStockLocations(created?.id, allowedWarehouses);

            } else {
                // Edit product (JSON fields)
                await Api.prodEdit({
                    id:parseInt(pid), name, sku:document.getElementById('pmSKU').value, barcode:document.getElementById('pmBarcode').value,
                    brand:brandValue, category:document.getElementById('pmCat').value,
                    description:document.getElementById('pmDesc').value, cost:parseFloat(document.getElementById('pmCost').value)||0,
                    supplier:document.getElementById('pmSupp').value,
                    allowedWarehouses:JSON.stringify(allowedWarehouses),
                    unitType:baseUnit,
                    packBaseUnit:baseUnit,
                    packCartonQty:packCartonQty,
                    packPaletteQty:packPaletteQty,
                    packBidonQty:packBidonQty,
                    packFutQty:packFutQty,
                    packPaquetQty:packPaquetQty,
                    packBoiteQty:packBoiteQty,
                    packTubeQty:packTubeQty,
                    packBobineQty:packBobineQty,
                    packRouleauQty:packRouleauQty,
                    packSeauQty:packSeauQty,
                    packSacQty:packSacQty,
                    packAerosolQty:packAerosolQty,
                    safetyStock:document.getElementById('pmSafety').value===''?null:parseInt(document.getElementById('pmSafety').value),
                    trackLot:document.getElementById('pmTrackLot').checked,
                    trackExpiry:document.getElementById('pmTrackExp').checked,
                    shelfLifeDays:document.getElementById('pmShelf').value===''?null:parseInt(document.getElementById('pmShelf').value),
                    datasheetUrl:document.getElementById('pmDatasheet').value,
                    msdsUrl:document.getElementById('pmMsds').value
                });

                // If a new image was selected during edit, upload it and update the stored URL.
                if(imgFile){
                    const fd = new FormData();
                    fd.append('id', pid);
                    fd.append('image', imgFile);
                    await Api.prodEditImg(fd);
                } else if(imgUrlOnly !== null){
                    // URL externe : mettre à jour directement le champ image
                    await Api.prodEdit({ id: parseInt(pid), image: imgUrlOnly });
                } else if(this._imgUploadUrl === ''){
                    // Image supprimée
                    await Api.prodEdit({ id: parseInt(pid), image: '' });
                }
                await this._syncStockLocations(pid, allowedWarehouses);
            }
            toast(pid?'Produit mis à jour':'Produit créé','success');
            Modal.close(); this.load();
        } catch(e){toast(e.message,'error');}
    },
    async delProduct(pid){
        if(!confirm('Supprimer ce produit ? (irréversible)')) return;
        try { await Api.prodDel(pid); toast('Produit supprimé','success'); Modal.close(); this.load(); }
        catch(e){toast(e.message,'error');}
    },
    // ── CSV Import ──────────────────────────────────────────────────────────
    openImportModal() {
        let modal = document.getElementById('csvImportModal');
        if (!modal) {
            modal = document.createElement('div');
            modal.id = 'csvImportModal';
            modal.className = 'modal-overlay';
            modal.innerHTML = `
            <div class="modal-box" style="max-width:600px;width:95%">
                <div class="modal-hd">
                    <h3>📂 Import produits CSV</h3>
                    <button class="modal-close" onclick="document.getElementById('csvImportModal').style.display='none'">×</button>
                </div>
                <div class="modal-bd">
                    <div style="background:var(--bg2);border-radius:8px;padding:1rem;margin-bottom:1rem;font-size:.85rem">
                        <strong>Format (séparateur ; ou ,) :</strong><br>
                        <code>name;sku;barcode;brand;category;description;cost;unitType;safetyStock</code><br>
                        Colonne obligatoire : <strong>name</strong> — Les catégories inexistantes sont créées auto.
                    </div>
                    <div style="margin-bottom:1rem">
                        <label class="form-label">Fichier CSV *</label>
                        <input type="file" id="csvFileInput" class="form-control" accept=".csv,.txt">
                    </div>
                    <div id="csvPreviewResult" style="max-height:250px;overflow-y:auto"></div>
                </div>
                <div class="modal-ft">
                    <button class="btn btn-secondary" id="btnDryRun">🔍 Vérifier</button>
                    <button class="btn btn-primary" id="btnDoImport" style="display:none">✅ Importer</button>
                    <button class="btn btn-secondary" onclick="document.getElementById('csvImportModal').style.display='none'">Annuler</button>
                </div>
            </div>`;
            document.body.appendChild(modal);
            document.getElementById('btnDryRun').onclick   = () => ProdPage.runImport('dryrun');
            document.getElementById('btnDoImport').onclick = () => ProdPage.runImport('import');
        }
        document.getElementById('csvFileInput').value = '';
        document.getElementById('csvPreviewResult').innerHTML = '';
        document.getElementById('btnDoImport').style.display = 'none';
        modal.style.display = 'flex';
    },

    async runImport(mode) {
        const file = document.getElementById('csvFileInput')?.files?.[0];
        if (!file) { toast('Sélectionnez un fichier CSV', 'error'); return; }
        const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || '';
        const fd = new FormData();
        fd.append('csv', file);
        fd.append('mode', mode);
        const previewEl = document.getElementById('csvPreviewResult');
        previewEl.innerHTML = '<p>Traitement…</p>';
        try {
            const resp = await fetch('api/import.php', { method:'POST', headers:{'X-CSRF-TOKEN':csrfToken}, body:fd });
            const data = await resp.json();
            if (!resp.ok) throw new Error(data.error || 'Erreur');
            const summary = `<div style="display:flex;gap:1rem;margin-bottom:.5rem;flex-wrap:wrap;font-size:.85rem">
                <span style="color:var(--green)">✅ Créés : <strong>${data.created}</strong></span>
                <span style="color:var(--orange)">⏭ Ignorés : <strong>${data.skipped}</strong></span>
                <span style="color:var(--red)">❌ Erreurs : <strong>${data.errors}</strong></span>
            </div>`;
            const rows = (data.results||[]).slice(0,50).map(r=>{
                const c=r.status==='ok'?'var(--green)':r.status==='skip'?'var(--orange)':'var(--red)';
                const i=r.status==='ok'?'✅':r.status==='skip'?'⏭':'❌';
                return `<div style="color:${c};font-size:.8rem">L.${r.line} ${i} ${r.name} — ${r.message}</div>`;
            }).join('');
            previewEl.innerHTML = summary + rows;
            if (mode==='dryrun') {
                document.getElementById('btnDoImport').style.display = data.created>0 ? 'inline-flex' : 'none';
            } else {
                toast(data.created+' produit(s) importé(s)','success');
                document.getElementById('csvImportModal').style.display = 'none';
                await this.load();
            }
        } catch(e) { previewEl.innerHTML = `<p style="color:var(--red)">${e.message}</p>`; }
    },

    // ──────────────────────────────────────────────────────────────
    // Zoom image : hover PC → lightbox plein écran | tap mobile
    // mouseenter/mouseleave directs sur chaque thumb après rendu (pas de délégation mouseover qui clignote)
    _initImageZoom(){
        if(!document.getElementById('imgZoomOverlay')){
            const ov = document.createElement('div');
            ov.id = 'imgZoomOverlay';
            ov.innerHTML = `
                <div id="imgZoomBg"></div>
                <img id="imgZoomImg" src="" alt="">
                <button id="imgZoomClose" title="Fermer">✕</button>
            `;
            document.body.appendChild(ov);
            document.getElementById('imgZoomClose').addEventListener('click', ()=>ProdPage._closeZoom());
            document.addEventListener('keydown', e=>{ if(e.key==='Escape') ProdPage._closeZoom(); });
            ov.addEventListener('mouseleave', ()=>ProdPage._closeZoom());
        }

        // Mobile/tablette : tap
        const pg = document.getElementById('pgProducts');
        if(!pg) return;
        pg.addEventListener('touchend', e=>{
            const thumb = e.target.closest('.prod-thumb-zoomable, .prod-list-img-trigger');
            if(!thumb) return;
            const img = thumb.querySelector('img');
            if(!img) return;
            e.preventDefault();
            e.stopPropagation();
            const ov = document.getElementById('imgZoomOverlay');
            if(ov.classList.contains('visible')){
                ProdPage._closeZoom();
            } else {
                ProdPage._openZoom(img.src, img.alt);
                clearTimeout(ov._tapTimer);
                ov._tapTimer = setTimeout(()=>ProdPage._closeZoom(), 2500);
            }
        }, { passive: false });
    },

    _openZoom(src, alt){
        if(!src || src.endsWith('undefined') || src === window.location.href) return;
        const ov  = document.getElementById('imgZoomOverlay');
        const img = document.getElementById('imgZoomImg');
        img.src = src;
        img.alt = alt || '';
        ov.classList.add('visible');
    },

    _closeZoom(){
        const ov = document.getElementById('imgZoomOverlay');
        if(ov) ov.classList.remove('visible');
    },

    // Appelé après chaque rendu (grille ET liste) pour attacher hover sur chaque thumb
    _attachImageHover(){
        const HOVER_OPEN_DELAY = 450;
        const isDesktopPointer = () => window.matchMedia && window.matchMedia('(hover: hover) and (pointer: fine)').matches;

        document.querySelectorAll(
            '#pgProducts .prod-thumb-zoomable, #pgProducts .prod-list-img-trigger'
        ).forEach(thumb=>{
            if(thumb._hoverBound) return;
            thumb._hoverBound = true;
            let _t = null;

            const clearHoverTimer = ()=>{
                if(_t){
                    clearTimeout(_t);
                    _t = null;
                }
            };

            thumb.addEventListener('mouseenter', ()=>{
                if(!isDesktopPointer()) return;
                const img = thumb.querySelector('img');
                if(!img || !img.naturalWidth) return;
                clearHoverTimer();
                _t = setTimeout(()=>{
                    ProdPage._openZoom(img.currentSrc || img.src, img.alt);
                    _t = null;
                }, HOVER_OPEN_DELAY);
            });

            thumb.addEventListener('mouseleave', e=>{
                clearHoverTimer();
                const to = e.relatedTarget;
                const ov = document.getElementById('imgZoomOverlay');
                // Ne ferme pas si la souris va vers l'overlay
                if(to && ov && (to === ov || ov.contains(to))) return;
                ProdPage._closeZoom();
            });

            thumb.addEventListener('mousedown', clearHoverTimer);
            thumb.addEventListener('focusout', clearHoverTimer);
        });
    },

};
