/**
 * Fichier  : assets/js/pages/movements.js
 * Auteur   : KREMER Régis
 * Version  : 2.6.98
 * Date     : 2026-07-20
 * Description : JS — Mouvements de stock : liste, filtres, saisie multi-type
 * Changelog :
 *   v2.6.23 (2026-07-08) — Impression mouvements A4 : liste filtrée, sélection BL, trame commune.
 *   v2.6.21 (2026-07-08) — UX sélection BL : cases compactes, sélection page, barre d’action synchronisée.
 *   v2.6.4 (2026-07-08) — Parcours terrain mobile/tablette : lignes mouvements convertibles en cartes, filtres/actions tactiles.
 *   v2.3.16 (2026-06-02) — BL simple : modale large, lisible, cohérente thème clair, champs accessibles.
 *   v2.3.14 (2026-06-02) — BL simplifié : sélection de sorties, modale unique, génération directe.
 *   v2.3.11 (2026-06-02) — BL groupé par sélection multiple de sorties depuis la liste mouvements.
 *   v2.3.1 (2026-06-02) — Correction ajout stock : appel Api.mvAdd au lieu de l'alias absent.
 *   v2.6.70 (2026-07-11) — I18N CORE LOCALE CURRENCY1 : dates et coûts selon le format régional actif.
 *   v2.6.90 (2026-07-20) — SCANNER CONSUMERS MIGRATION3 : Mouvements reliés au composant scanner commun.
 *
 * 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
 *   v2.6.92 (2026-07-20) — SCANNER UX POLISH OPTIONAL1 : feedback sonore/haptique commun sans doublon.
 *   v2.6.98 (2026-07-20) — SCANNER TERRAIN CONFIRMATION1 : accès caméra sans emoji ni glyphe système.
 */
// ================================================================
// movements.js — Full movement history with SCANNER support
// ================================================================
const MvPage = {
    pageIndex: 1,
    pageSize: 20,
    _all: [],
    _filtered: [],
    _selectedDeliveryMoves: new Set(),
    _deliverySelectionBound: false,
    contextProduct: null,  // NEW: Pre-fill context
    contextWarehouse: null, // NEW: Pre-fill context

    async init(){
        const [whs, prods, suppliers, stock] = await Promise.all([
            Api.warehouses(), 
            Api.products(), 
            Api.suppliers(),
            Api.stock()
        ]);
        this.whs = whs; 
        this.prods = prods;
        this.suppliers = suppliers || [];
        this.stock = stock || [];
        
        // ── Filtre magasin ───────────────────────────────────────
        const whSel = document.getElementById('mvWhFilter');
        if(whSel) whSel.innerHTML = '<option value="">Tous les magasins</option>' +
            whs.map(w=>`<option value="${w.id}">${w.name}</option>`).join('');

        // ── Filtre produit (cascade : se met à jour selon le magasin) ──
        const prodSel = document.getElementById('mvProdFilter');
        const _buildProdOptions = (wid) => {
            if(!prodSel) return;
            let available = prods;
            if(wid) {
                // Pour filtrer les produits : ceux qui ont du stock dans ce magasin
                // OU tous si on veut aussi voir les mouvements passés
                const inWh = new Set((stock||[])
                    .filter(s=>String(s.warehouseId)===String(wid))
                    .map(s=>String(s.productId)));
                // On filtre sur ceux qui ont/ont eu du stock dans ce magasin
                // (on utilise _all si dispo, sinon tous les produits)
                const seen = new Set(
                    (this._all||[])
                        .filter(m=>String(m.warehouseId)===String(wid))
                        .map(m=>String(m.productId))
                );
                available = prods.filter(p=>inWh.has(String(p.id))||seen.has(String(p.id)));
                if(!available.length) available = prods; // fallback
            }
            const cur = prodSel.value;
            prodSel.innerHTML = '<option value="">Tous les produits</option>' +
                available.map(p=>{
                    const sku = p.sku ? ` — ${p.sku}` : '';
                    return `<option value="${p.id}"${String(p.id)===cur?' selected':''}>${p.name}${sku}</option>`;
                }).join('');
        };
        _buildProdOptions('');
        if(whSel) whSel.addEventListener('change', ()=>{
            _buildProdOptions(whSel.value);
            // Reset produit quand on change de magasin
            if(prodSel) prodSel.value='';
            this.load();
        });
        if(prodSel) prodSel.addEventListener('change', ()=>this.load());

        // ── Filtres avancés ──────────────────────────────────────
        ['mvOpFilter','mvSiteFilter','mvDeliveryFilter','mvSupplierFilter'].forEach(id=>{
            const el = document.getElementById(id);
            if(el) el.addEventListener('input', ()=>this.load());
        });

        // ── Dates par défaut : 30 derniers jours ─────────────────
        const today = new Date();
        const from  = new Date(today);
        from.setDate(from.getDate()-30);
        document.getElementById('mvDateFrom').value = from.toISOString().slice(0,10);
        document.getElementById('mvDateTo').value   = today.toISOString().slice(0,10);

        // ── Événements filtres principaux ────────────────────────
        ['mvSearch','mvTypeFilter','mvDateFrom','mvDateTo'].forEach(id=>{
            const el = document.getElementById(id);
            if(el) el.addEventListener('input', ()=>this.load());
        });

        // ── Reset ────────────────────────────────────────────────
        document.getElementById('btnMvReset').onclick = ()=>{
            ['mvSearch','mvOpFilter','mvSiteFilter','mvDeliveryFilter','mvSupplierFilter'].forEach(id=>{
                const el=document.getElementById(id); if(el) el.value='';
            });
            if(document.getElementById('mvTypeFilter')) document.getElementById('mvTypeFilter').value='';
            if(whSel) { whSel.value=''; _buildProdOptions(''); }
            if(prodSel) prodSel.value='';
            document.getElementById('mvDateFrom').value = from.toISOString().slice(0,10);
            document.getElementById('mvDateTo').value   = today.toISOString().slice(0,10);
            this.load();
        };
        
        // Quick action buttons (admin)
        const btnAdd = document.getElementById('btnMvAdd');
        const btnRem = document.getElementById('btnMvRemove');
        const btnTr  = document.getElementById('btnMvTransfer');
        const btnEntry = document.getElementById('btnMvEntry');
        
        if(btnAdd) btnAdd.onclick = ()=> this.openMvModal('add');
        if(btnRem) btnRem.onclick = ()=> this.openMvModal('remove');
        if(btnTr)  btnTr.onclick  = ()=> this.openMvModal('transfer');
        if(btnEntry) btnEntry.onclick = ()=> this.openMvModal('entry');
        const btnPrint = document.getElementById('btnMvPrint');
        if(btnPrint) btnPrint.onclick = () => this.printMovements();

        // Advanced filters toggle
        const advBtn = document.getElementById('btnMvAdvanced');
        const advWrap = document.getElementById('mvAdvancedWrap');
        if(advBtn && advWrap){
            advBtn.onclick = ()=>{
                const open = advWrap.style.display !== 'none';
                advWrap.style.display = open ? 'none' : 'block';
                advBtn.textContent = open ? 'Filtres +' : 'Filtres −';
            };
        }
        
        // Bons de livraison groupés — sélection multiple
        this._initDeliverySelection();

        // Pagination events
        const prevBtn = document.getElementById('mvPrev');
        const nextBtn = document.getElementById('mvNext');
        const sizeSel = document.getElementById('mvPageSize');
        
        if(prevBtn) prevBtn.onclick = () => {
            if(this.pageIndex > 1) {
                this.pageIndex--;
                this.renderTable();
            }
        };
        if(nextBtn) nextBtn.onclick = () => {
            const total = Math.ceil(this._filtered.length / this.pageSize);
            if(this.pageIndex < total) {
                this.pageIndex++;
                this.renderTable();
            }
        };
        if(sizeSel) {
            sizeSel.onchange = () => {
                this.pageSize = parseInt(sizeSel.value) || 20;
                this.pageIndex = 1;
                this.renderTable();
            };
        }
        
        // ══════════════════════════════════════════════════════════════
        // INFINITE SCROLL - Init pour mobile/tablette
        // ══════════════════════════════════════════════════════════════
        this._initInfiniteScroll();

        await this.load();

        // ── Tri des colonnes mouvements ──────────────────────────
        if(typeof TableSort !== 'undefined'){
            TableSort.init('mvTable',
                () => this._filteredRaw || [],
                (sorted) => {
                    this._filteredRaw = sorted;
                    this._filtered = this._groupForRender(sorted);
                    this.pageIndex = 1;
                    this.renderTable();
                },
                {
                    defaultKey: 'date',
                    defaultDir: 'desc',
                    getters: {
                        __product:   m => (this.prods.find(p=>p.id===m.productId)||{}).name||'',
                        __sku:       m => (this.prods.find(p=>p.id===m.productId)||{}).sku||'',
                        __warehouse: m => (this.whs.find(w=>w.id==m.warehouseId)||{}).name||'',
                        __stock:     m => this._getCurrentStock(m.productId, m.warehouseId),
                        date:        m => new Date(m.date).getTime(),
                        quantity:    m => Math.abs(m.quantity||0),
                    }
                }
            );
        }
        const modalType = sessionStorage.getItem('mvModalType');
        const modalProduct = sessionStorage.getItem('mvModalProduct');
        const modalWarehouse = sessionStorage.getItem('mvModalWarehouse');
        
        if(modalType) {
            // Clear sessionStorage
            sessionStorage.removeItem('mvModalType');
            sessionStorage.removeItem('mvModalProduct');
            sessionStorage.removeItem('mvModalWarehouse');
            
            // Open modal with prefill
            setTimeout(() => {
                this.openMvModal(
                    modalType,
                    modalProduct ? parseInt(modalProduct) : null,
                    modalWarehouse ? parseInt(modalWarehouse) : null
                );
            }, 100);
        }
    },

    async load(){
        const q       = (document.getElementById('mvSearch')?.value||'').toLowerCase();
        const typeF   = document.getElementById('mvTypeFilter')?.value||'';
        const whF     = parseInt(document.getElementById('mvWhFilter')?.value)||0;
        const pidF    = parseInt(document.getElementById('mvProdFilter')?.value)||0;
        const dateFrom= document.getElementById('mvDateFrom')?.value||'';
        const dateTo  = document.getElementById('mvDateTo')?.value||'';
        // Filtres avancés
        const opF     = (document.getElementById('mvOpFilter')?.value||'').toLowerCase();
        const siteF   = (document.getElementById('mvSiteFilter')?.value||'').toLowerCase();
        const blF     = (document.getElementById('mvDeliveryFilter')?.value||'').toLowerCase();
        const suppF   = (document.getElementById('mvSupplierFilter')?.value||'').toLowerCase();

        const params = {};
        if(dateFrom) params.dateFrom = dateFrom;
        if(dateTo)   params.dateTo   = dateTo;
        if(typeF)    params.type     = typeF;
        if(whF)      params.warehouseId = whF;
        if(pidF)     params.productId   = pidF;

        const mvs = await Api.movements(params);
        this._all = mvs;

        // Mettre à jour le filtre produit selon le magasin (après chargement)
        const prodSel = document.getElementById('mvProdFilter');
        if(prodSel && whF) {
            const seen = new Set(mvs.map(m=>String(m.productId)));
            const available = this.prods.filter(p=>seen.has(String(p.id)));
            if(available.length) {
                const cur = prodSel.value;
                prodSel.innerHTML = '<option value="">Tous les produits</option>' +
                    available.map(p=>{
                        const sku = p.sku ? ` — ${p.sku}` : '';
                        return `<option value="${p.id}"${String(p.id)===cur?' selected':''}>${p.name}${sku}</option>`;
                    }).join('');
            }
        }

        let filtered = [...mvs];

        // Filtre texte libre
        if(q) filtered = filtered.filter(m=>{
            const p = this.prods.find(x=>x.id===m.productId);
            const w = this.whs.find(x=>x.id==m.warehouseId);
            return (p?.name||'').toLowerCase().includes(q)||
                   (p?.sku||'').toLowerCase().includes(q)||
                   (w?.name||'').toLowerCase().includes(q)||
                   (m.notes||'').toLowerCase().includes(q);
        });

        // Filtres avancés (côté client)
        if(opF)   filtered = filtered.filter(m=>(m.operator||m.user||'').toLowerCase().includes(opF));
        if(siteF) filtered = filtered.filter(m=>(m.notes||'').toLowerCase().includes(siteF));
        if(blF)   filtered = filtered.filter(m=>(m.deliveryNote||m.notes||'').toLowerCase().includes(blF));
        if(suppF) filtered = filtered.filter(m=>(m.supplier||'').toLowerCase().includes(suppF));

        this._filtered = this._groupForRender(filtered);
        if(this._selectedDeliveryMoves){
            const validIds = new Set(filtered.filter(m => this._isDeliverySelectable(m)).map(m => String(m.id)));
            [...this._selectedDeliveryMoves].forEach(id => { if(!validIds.has(String(id))) this._selectedDeliveryMoves.delete(id); });
        }
        this._filteredRaw = filtered; // Conserver données brutes pour le tri
        // Appliquer tri actif
        if(typeof TableSort !== 'undefined'){
            this._filteredRaw = TableSort.sortData('mvTable', filtered);
            this._filtered = this._groupForRender(this._filteredRaw);
        }
        this.pageIndex = 1;
        this.renderTable();
    },

    // Helper pour obtenir le stock actuel d'un produit dans un magasin
    _getCurrentStock(productId, warehouseId) {
        const stockEntry = (this.stock || []).find(s => 
            String(s.productId) === String(productId) && 
            String(s.warehouseId) === String(warehouseId)
        );
        return stockEntry ? (parseInt(stockEntry.quantity) || 0) : 0;
    },


    _escapeHtml(value){
        return String(value ?? '').replace(/[&<>"]|'/g, (ch) => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[ch]));
    },

    _isDeliverySelectable(m){
        if(!m || m.__group) return false;
        return ['remove','transfer'].includes(m.type);
    },

    _getMovementById(id){
        const sid = String(id);
        return (this._all || []).find(m => String(m.id) === sid) || null;
    },

    _selectedDeliveryList(){
        return [...(this._selectedDeliveryMoves || new Set())]
            .map(id => this._getMovementById(id))
            .filter(Boolean)
            .filter(m => this._isDeliverySelectable(m));
    },

    _initDeliverySelection(){
        if(!this._selectedDeliveryMoves) this._selectedDeliveryMoves = new Set();

        const bulkBtn = document.getElementById('btnMvCreateGroupedBl');
        const clearBtn = document.getElementById('btnMvClearGroupedBl');
        const selectPageBtn = document.getElementById('btnMvSelectPageBl');
        const pageToggle = document.getElementById('mvDeliveryPageToggle');
        if(bulkBtn) bulkBtn.onclick = () => this.openGroupedDeliveryModal();
        if(clearBtn) clearBtn.onclick = () => this.clearDeliverySelection();
        if(selectPageBtn) selectPageBtn.onclick = () => this.selectVisibleDeliveryRows();
        if(pageToggle) pageToggle.onchange = () => {
            if(pageToggle.checked) this.selectVisibleDeliveryRows(false);
            else this.unselectVisibleDeliveryRows(false);
            this.renderTable();
        };

        if(!this._deliverySelectionBound){
            document.addEventListener('change', (ev) => {
                const cb = ev.target?.closest?.('.mv-delivery-check');
                if(!cb) return;
                const id = String(cb.dataset.id || '');
                const mv = this._getMovementById(id);
                if(!id || !this._isDeliverySelectable(mv)) {
                    cb.checked = false;
                    return;
                }
                if(cb.checked) this._selectedDeliveryMoves.add(id);
                else this._selectedDeliveryMoves.delete(id);
                this.updateDeliverySelectionBar();
            });
            this._deliverySelectionBound = true;
        }
    },

    _currentRenderedDeliveryRows(){
        const list = this._filtered || [];
        if(typeof InfiniteScroll !== 'undefined' && InfiniteScroll.isMobileMode()){
            return list.filter(m => !m.__group && this._isDeliverySelectable(m));
        }
        const start = (this.pageIndex - 1) * this.pageSize;
        return list.slice(start, start + this.pageSize).filter(m => !m.__group && this._isDeliverySelectable(m));
    },

    updateDeliverySelectionBar(){
        const bar = document.getElementById('mvDeliveryBulkBar');
        const countEl = document.getElementById('mvDeliveryBulkCount');
        const selected = this._selectedDeliveryList();
        const count = selected.length;
        if(bar){
            bar.hidden = count === 0;
            bar.classList.toggle('is-active', count > 0);
        }
        if(countEl) countEl.textContent = `${count} sortie${count > 1 ? 's' : ''} sélectionnée${count > 1 ? 's' : ''}`;

        document.querySelectorAll('.mv-delivery-check').forEach(cb => {
            const id = String(cb.dataset.id || '');
            const checked = this._selectedDeliveryMoves.has(id);
            cb.checked = checked;
            const label = cb.closest('.mv-delivery-select');
            if(label) label.classList.toggle('is-checked', checked);
            const row = cb.closest('tr[data-movement-id]');
            if(row) row.classList.toggle('is-delivery-selected', checked);
        });

        const pageToggle = document.getElementById('mvDeliveryPageToggle');
        if(pageToggle){
            const visible = this._currentRenderedDeliveryRows();
            const checkedCount = visible.filter(m => this._selectedDeliveryMoves.has(String(m.id))).length;
            pageToggle.checked = visible.length > 0 && checkedCount === visible.length;
            pageToggle.indeterminate = checkedCount > 0 && checkedCount < visible.length;
            pageToggle.disabled = visible.length === 0;
        }
    },

    clearDeliverySelection(){
        this._selectedDeliveryMoves.clear();
        this.updateDeliverySelectionBar();
    },

    selectVisibleDeliveryRows(rerender = true){
        this._currentRenderedDeliveryRows().forEach(m => this._selectedDeliveryMoves.add(String(m.id)));
        if(rerender) this.renderTable();
        else this.updateDeliverySelectionBar();
    },

    unselectVisibleDeliveryRows(rerender = true){
        this._currentRenderedDeliveryRows().forEach(m => this._selectedDeliveryMoves.delete(String(m.id)));
        if(rerender) this.renderTable();
        else this.updateDeliverySelectionBar();
    },

    _renderDeliveryCheckbox(m){
        if(!this._isDeliverySelectable(m)) return '<span class="mv-delivery-na" title="Non sélectionnable pour un bon de livraison">—</span>';
        const id = String(m.id || '');
        const checked = this._selectedDeliveryMoves?.has(id) ? ' checked' : '';
        const selectedClass = checked ? ' is-checked' : '';
        const p = this.prods.find(x => String(x.id) === String(m.productId));
        const label = `Ajouter ${p?.name || 'cette sortie'} au bon de livraison`;
        return `<label class="mv-delivery-select${selectedClass}" title="${this._escapeHtml(label)}">
            <input type="checkbox" class="mv-delivery-check" data-id="${this._escapeHtml(id)}" aria-label="${this._escapeHtml(label)}"${checked}>
            <span class="mv-delivery-mark" aria-hidden="true"></span>
        </label>`;
    },


    _deliverySubjectOptions(){
        return [
            'Dotation EPI',
            'Sortie chantier',
            'Remise matériel',
            'Réassort service',
            'Prêt matériel',
            'Remplacement matériel',
            'Autre'
        ];
    },

    _deliveryDestinationOptions(selected = []){
        return ['Voirie', 'Signalisation', 'Propreté', 'Parc auto', 'E.P.I', 'Atelier', 'Magasin', 'Chantier', 'Agent', 'Autre'];
    },

    _formatMovementQty(qty, unit = 'piece'){
        const n = Math.abs(Number(qty || 0));
        const formatted = Number.isInteger(n) ? String(n) : String(n).replace('.', ',');
        const raw = String(unit || 'piece').trim();
        const lower = raw.toLowerCase();
        let label = raw;
        if(['u','unité','unite','unit'].includes(lower)) label = n > 1 ? 'unités' : 'unité';
        else if(['piece','pièce','pieces','pièces'].includes(lower)) label = n > 1 ? 'pièces' : 'pièce';
        else if(n > 1 && !/[sx]$/i.test(label)) label += 's';
        return `${formatted} ${label}`;
    },

    _movementTypeLabel(type){
        return ({add:'Ajout', remove:'Sortie', transfer:'Transfert', entry:'Entrée', inventory:'Inventaire'}[type] || type || '—');
    },

    _movementTypeBadge(type){
        const label = this._movementTypeLabel(type).toUpperCase();
        const cls = ({add:'success', remove:'danger', transfer:'info', entry:'success', inventory:'warning'}[type] || 'secondary');
        return `<span class="badge badge-${cls}">${this._escapeHtml(label)}</span>`;
    },

    _formatMovementDate(value){
        if(!value) return '—';
        const d = new Date(value);
        if(Number.isNaN(d.getTime())) return this._escapeHtml(value);
        return Locale.formatDateTime(d);
    },

    _currentMovementPrintRows(){
        const raw = Array.isArray(this._filteredRaw) ? this._filteredRaw : [];
        if(raw.length) return raw.filter(m => !m.__group);
        const fallback = (this._filtered || []).filter(m => !m.__group);
        return fallback;
    },

    _printFiltersMeta(){
        const whId = document.getElementById('mvWhFilter')?.value || '';
        const prodId = document.getElementById('mvProdFilter')?.value || '';
        const type = document.getElementById('mvTypeFilter')?.value || '';
        const dateFrom = document.getElementById('mvDateFrom')?.value || '';
        const dateTo = document.getElementById('mvDateTo')?.value || '';
        const wh = whId ? (this.whs.find(w => String(w.id) === String(whId))?.name || whId) : 'Tous les magasins';
        const prod = prodId ? (this.prods.find(p => String(p.id) === String(prodId))?.name || prodId) : 'Tous les produits';
        const period = [dateFrom || '—', dateTo || '—'].join(' → ');
        return [
            {label:'Magasin', value: wh},
            {label:'Produit', value: prod},
            {label:'Type', value: type ? this._movementTypeLabel(type) : 'Tous les types'},
            {label:'Période', value: period},
        ];
    },

    printMovements(){
        const rows = this._currentMovementPrintRows();
        if(!rows.length){
            if(window.toast) toast('Aucun mouvement à imprimer avec les filtres actuels.', 'warning');
            else alert('Aucun mouvement à imprimer avec les filtres actuels.');
            return;
        }
        const totalIn = rows.filter(m => ['add','entry'].includes(m.type)).reduce((s,m)=>s + Math.abs(Number(m.quantity || 0)), 0);
        const totalOut = rows.filter(m => m.type === 'remove').reduce((s,m)=>s + Math.abs(Number(m.quantity || 0)), 0);
        const transfers = rows.filter(m => m.type === 'transfer').length;
        const bodyRows = rows.map(m => {
            const p = this.prods.find(x => String(x.id) === String(m.productId)) || {};
            const w = this.whs.find(x => String(x.id) === String(m.warehouseId)) || {};
            const dst = this.whs.find(x => String(x.id) === String(m.transferTo || '')) || null;
            const qtyClass = m.type === 'remove' ? 'text-danger' : (m.type === 'transfer' ? 'text-info' : 'text-success');
            const qtySign = m.type === 'remove' ? '-' : (m.type === 'transfer' ? '↔ ' : '+');
            const reason = [m.reason, m.site].filter(Boolean).join(' · ') || '—';
            const destination = dst ? `<br><span class="ms-print-muted">→ ${this._escapeHtml(dst.name || '')}</span>` : '';
            return `<tr>
                <td>${this._escapeHtml(this._formatMovementDate(m.date))}</td>
                <td>${this._movementTypeBadge(m.type)}</td>
                <td><strong>${this._escapeHtml(p.name || '?')}</strong><div class="ms-print-muted">${this._escapeHtml(p.sku || p.barcode || '—')}</div></td>
                <td>${this._escapeHtml(w.name || '—')}${destination}</td>
                <td class="tr bold ${qtyClass}">${this._escapeHtml(qtySign + this._formatMovementQty(m.quantity, m.unit || 'piece'))}</td>
                <td>${this._escapeHtml(reason)}</td>
                <td>${this._escapeHtml(m.operator || '—')}</td>
            </tr>`;
        }).join('');

        const table = `<table class="ms-print-movements-table"><thead><tr>
            <th>Date</th><th>Type</th><th>Produit</th><th>Magasin</th><th class="tr">Quantité</th><th>Raison / chantier</th><th>Opérateur</th>
        </tr></thead><tbody>${bodyRows}</tbody></table>`;

        if(window.MageStockPrint?.open){
            MageStockPrint.open({
                title: 'Mouvements de stock',
                subtitle: `${rows.length} mouvement${rows.length > 1 ? 's' : ''} affiché${rows.length > 1 ? 's' : ''}`,
                reference: 'Mouvements',
                meta: this._printFiltersMeta(),
                summary: [
                    {label:'Mouvements', value: rows.length},
                    {label:'Entrées / ajouts', value: totalIn},
                    {label:'Sorties', value: totalOut},
                    {label:'Transferts', value: transfers},
                ],
                bodyHtml: table + '<div class="ms-print-signatures"><div class="ms-print-signature">Édité par</div><div class="ms-print-signature">Visa / contrôle</div></div>',
                orientation: rows.length > 12 ? 'landscape' : 'portrait',
                footer: 'Liste des mouvements imprimée depuis MageStock',
                autoPrint: false,
            });
            return;
        }
        const w = window.open('', '_blank', 'width=1100,height=800');
        if(!w) return;
        w.document.write('<!doctype html><html><head><title>Mouvements</title></head><body>'+table+'</body></html>');
        w.document.close();
    },

    _warehouseNamesForMovements(movements = []){
        return [...new Set(movements.map(m => {
            const w = this.whs.find(x => String(x.id) === String(m.warehouseId));
            return w?.name || m.warehouseId || '';
        }).filter(Boolean))];
    },

    _renderDeliverySubjectField(id, value = ''){
        const opts = this._deliverySubjectOptions();
        const safeValue = value || opts[0];
        return `<select class="form-select" id="${id}">
            ${opts.map(o => `<option value="${this._escapeHtml(o)}" ${o===safeValue?'selected':''}>${this._escapeHtml(o)}</option>`).join('')}
        </select>`;
    },

    _renderDeliveryDestinationField(id, selected = [], value = ''){
        const opts = this._deliveryDestinationOptions(selected);
        return `<input type="text" class="form-input" id="${id}" list="${id}List" placeholder="Ex : Atelier, service, chantier, agent…" value="${this._escapeHtml(value || '')}">
            <datalist id="${id}List">${opts.map(o => `<option value="${this._escapeHtml(o)}"></option>`).join('')}</datalist>`;
    },

    _buildGroupedDeliveryUrl(options = {}){
        const selected = this._selectedDeliveryList();
        const ids = selected.map(m => String(m.id)).join(',');
        const params = new URLSearchParams();
        params.set('ids', ids);
        if(options.recipient) params.set('recipient', options.recipient);
        if(options.destination) params.set('destination', options.destination);
        if(options.subject) params.set('subject', options.subject);
        if(options.merge) params.set('merge', '1');
        if(options.images) params.set('images', '1');
        return Api.url(`api/delivery_note.php?${params.toString()}`);
    },

    openGroupedDeliveryModal(){
        const selected = this._selectedDeliveryList();
        if(!selected.length){
            if(window.toast) toast('Coche au moins une sortie.', 'warning');
            else alert('Coche au moins une sortie.');
            return;
        }

        const warehouseNames = this._warehouseNamesForMovements(selected);
        const operators = [...new Set(selected.map(m => (m.operator || '').trim()).filter(Boolean))];
        const suggestedRecipient = operators.length === 1 ? operators[0] : '';

        const rows = selected.map(m => {
            const p = this.prods.find(x=>String(x.id)===String(m.productId)) || {};
            const w = this.whs.find(x=>String(x.id)===String(m.warehouseId)) || {};
            return `<tr>
                <td><strong>${this._escapeHtml(p.name || '?')}</strong><div class="muted">${this._escapeHtml(p.sku || '—')}</div></td>
                <td>${this._escapeHtml(w.name || '—')}</td>
                <td class="text-right">${this._escapeHtml(this._formatMovementQty(m.quantity, m.unit || 'piece'))}</td>
            </tr>`;
        }).join('');

        const whBlock = warehouseNames.length
            ? `<div class="mv-bl-warehouses"><strong>Magasin${warehouseNames.length>1?'s':''} source${warehouseNames.length>1?'s':''}</strong><div>${warehouseNames.map(w=>`<span>${this._escapeHtml(w)}</span>`).join('')}</div></div>`
            : '';

        const content = `
            <div class="mv-bl-modal mv-bl-modal-v216">
                <p class="mv-bl-help">Indique simplement à qui tu remets le matériel, choisis le type du bon, puis imprime. Les produits cochés seront regroupés dans un seul document.</p>
                ${whBlock}
                <div class="form-grid mv-bl-form-grid">
                    <label class="form-group">
                        <span class="form-label">Type de bon</span>
                        ${this._renderDeliverySubjectField('mvBlSubject', 'Sortie chantier')}
                    </label>
                    <label class="form-group">
                        <span class="form-label">Réceptionnaire *</span>
                        <input type="text" class="form-input" id="mvBlRecipient" placeholder="Nom de l'agent, du service ou de la personne" value="${this._escapeHtml(suggestedRecipient)}">
                    </label>
                    <label class="form-group mv-bl-form-wide">
                        <span class="form-label">Destination *</span>
                        ${this._renderDeliveryDestinationField('mvBlDestination', selected, '')}
                    </label>
                </div>
                <div class="mv-bl-preview">
                    <div class="mv-bl-preview-title"><span>${selected.length} ligne${selected.length>1?'s':''} sélectionnée${selected.length>1?'s':''}</span><small>Vérifie les produits avant création du BL</small></div>
                    <div class="table-container">
                        <table class="data-table mv-bl-table">
                            <thead><tr><th>Produit</th><th>Magasin</th><th class="text-right">Qté</th></tr></thead>
                            <tbody>${rows}</tbody>
                        </table>
                    </div>
                </div>
            </div>`;

        if(window.Modal?.show){
            const modalBox = document.getElementById('modalBox');
            if(modalBox) modalBox.classList.add('mv-bl-wide-modal');
            Modal.show('Créer un bon de livraison', content, [
                {label:'Annuler', cls:'btn-secondary', onclick:()=>{ this._closeGroupedDeliveryModal(); }},
                {label:'Créer le bon', cls:'btn-primary', onclick:()=>this._openGroupedDeliveryFromModal()}
            ], 'info');
            const closeBtn = document.getElementById('modalCloseBtn');
            if(closeBtn) closeBtn.addEventListener('click', () => this._closeGroupedDeliveryModal(), { once: true });
            setTimeout(() => document.getElementById('mvBlRecipient')?.focus(), 50);
        } else {
            const url = this._buildGroupedDeliveryUrl({merge:true, images:true});
            window.open(url, '_blank');
        }

    },

    _openGroupedDeliveryFromModal(){
        const recipient = (document.getElementById('mvBlRecipient')?.value || '').trim();
        const destination = (document.getElementById('mvBlDestination')?.value || '').trim();
        const subject = (document.getElementById('mvBlSubject')?.value || 'Bon de livraison').trim();
        if(!recipient){
            if(window.toast) toast('Indique le réceptionnaire.', 'warning');
            document.getElementById('mvBlRecipient')?.focus();
            return;
        }
        if(!destination){
            if(window.toast) toast('Indique la destination.', 'warning');
            document.getElementById('mvBlDestination')?.focus();
            return;
        }
        const url = this._buildGroupedDeliveryUrl({
            recipient,
            destination,
            subject,
            merge: true,
            images: true,
        });
        window.open(url, '_blank');
        if(window.toast) toast('Bon de livraison généré.', 'success');
        this.clearDeliverySelection();
        this._closeGroupedDeliveryModal();
    },

    _closeGroupedDeliveryModal(){
        if(window.Modal?.close) Modal.close();
        const modalBox = document.getElementById('modalBox');
        if(modalBox) modalBox.classList.remove('mv-bl-wide-modal');
    },


    renderTable(){
        const tbody = document.getElementById('mvBody');
        
        // ══════════════════════════════════════════════════════════════
        // MODE SCROLL INFINI (mobile/tablette)
        // ══════════════════════════════════════════════════════════════
        if(typeof InfiniteScroll !== 'undefined' && InfiniteScroll.isMobileMode()){
            const firstPage = this._filtered.slice(0, this.pageSize);
            
            InfiniteScroll.reset('movements', firstPage);
            InfiniteScroll.setTotal('movements', this._filtered.length);
            
            this._renderTableRows(firstPage, false);
            document.getElementById('mvCount').textContent = this._filtered.length + ' mouvement(s)';
            return;
        }
        
        // ══════════════════════════════════════════════════════════════
        // MODE PAGINATION CLASSIQUE (desktop)
        // ══════════════════════════════════════════════════════════════
        const start = (this.pageIndex-1)*this.pageSize;
        const page = this._filtered.slice(start, start+this.pageSize);

        if(!page.length){
            tbody.innerHTML = '<tr><td colspan="13" class="tbl-empty">Aucun mouvement</td></tr>';
            this.renderPagination();
            document.getElementById('mvCount').textContent = '0 mouvement(s)';
            return;
        }

        const typeLabel = {
            add: 'AJOUT',
            remove: 'SORTIE', 
            transfer: 'TRANSFERT',
            entry: 'ENTRÉE',
            inventory: 'INVENTAIRE'
        };
        const typeCls = {
            add: 'success',
            remove: 'danger',
            transfer: 'info',
            entry: 'success',
            inventory: 'warning'
        };

        tbody.innerHTML = page.map(m=>{
            
            if (m && m.__group) return this._renderGroupBlock(m);
const p = this.prods.find(x=>x.id===m.productId);
            const w = this.whs.find(x=>x.id==m.warehouseId);
            const date = Locale.formatDateTime(m.date);
            
            // Format quantity with sign
            const qtySign = m.type === 'remove' ? '-' : '+';
            const qtyClass = m.type === 'remove' ? 'text-danger' : 'text-success';
            const qtyDisplay = `<span class="${qtyClass}">${qtySign}${Math.abs(m.quantity)}</span>`;
            
            // Stock actuel (depuis les données de stock)
            const currentStock = this._getCurrentStock(m.productId, m.warehouseId);
            
            // Reason/Site combined
            const reasonSite = [m.reason, m.site].filter(Boolean).join(' · ') || '—';
            
// Document associé : BC ou BL selon contexte
const bcMatch = (m.reason || '').match(/\bBC-\d{8}-[A-Z0-9]+\b/);
const bcId    = bcMatch ? bcMatch[0] : '';
const docLabel = bcId || '—';

let docBtn = '—';
if (bcId) {
    // Réception BC → bouton vers le bon de commande
    const bcUrl = 'orders?open=' + encodeURIComponent(bcId);
    docBtn = `<button class="btn btn-xs btn-primary" title="Ouvrir le bon de commande ${bcId}"
        onclick="window.open('${bcUrl}','_blank')">
        <svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2.5" style="margin-right:3px;vertical-align:-1px"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>${bcId}</button>`;
} else if (['remove','transfer'].includes(m.type)) {
    // Sortie / transfert uniquement → BL individuel
    docBtn = `<button class="btn btn-xs btn-outline-secondary mv-btn-bl" title="Bon de livraison"
        onclick="window.open('${Api.url(`api/delivery_note.php?movementId=${m.id}`)}','_blank')">
        <svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2.5" style="margin-right:3px;vertical-align:-1px"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>BL</button>`;
    if (m.site) {
        // Sortie chantier → BL groupé par site
        const d = (m.date || '').slice(0, 10);
        const siteUrl = Api.url('api/delivery_note.php?site=' + encodeURIComponent(m.site) + '&date=' + encodeURIComponent(d));
        docBtn += `<button class="btn btn-xs btn-outline-info mv-btn-site" title="BL groupé — toutes les sorties du chantier ce jour"
            onclick="window.open('${siteUrl}','_blank')">
            <svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2.5" style="margin-right:3px;vertical-align:-1px"><circle cx="12" cy="10" r="3"/><path d="M12 2a8 8 0 00-8 8c0 5.4 7.05 11.5 7.72 12.07a.5.5 0 00.56 0C13 21.5 20 15.4 20 10a8 8 0 00-8-8z"/></svg>Site</button>`;
    }
}
            const selectedRowClass = this._selectedDeliveryMoves?.has(String(m.id || '')) ? ' class="is-delivery-selected"' : '';
            return `
                <tr${selectedRowClass} data-movement-id="${this._escapeHtml(m.id || '')}">
                    <td class="cell-center mv-select-cell" data-label="BL">${this._renderDeliveryCheckbox(m)}</td>
                    <td data-label="Date">${date}</td>
                    <td data-label="Type"><span class="badge badge-${typeCls[m.type]||'secondary'}">${typeLabel[m.type]||m.type}</span></td>
                    <td data-label="Produit"><strong>${p?.name||'?'}</strong></td>
                    <td class="col-mono" data-label="SKU">${p?.sku||'—'}</td>
                    <td data-label="Magasin">${w?.name||'?'}</td>
                    <td class="col-qty" data-label="Quantité">${qtyDisplay}</td>
                    <td class="col-stock" data-label="Stock">${currentStock}</td>
                    <td data-label="Raison / chantier">${reasonSite}</td>
                    <td data-label="Opérateur">${m.operator||'—'}</td>
                    <td data-label="Note">${m.note||'—'}</td>
                    <td data-label="Document">${docLabel}</td>
                    <td class="cell-center" data-label="Pièce">${docBtn}</td>
                </tr>
            `;
        }).join('');

        document.getElementById('mvCount').textContent = this._filtered.length + ' mouvement(s)';
        this.renderPagination();
        this.updateDeliverySelectionBar();
    },

    renderPagination(){
        const total = Math.ceil(this._filtered.length / this.pageSize);
        const pageInfo = document.getElementById('mvPageInfo');
        const prevBtn = document.getElementById('mvPrev');
        const nextBtn = document.getElementById('mvNext');
        
        if(pageInfo) pageInfo.textContent = `Page ${this.pageIndex} / ${Math.max(1, total)}`;
        if(prevBtn) prevBtn.disabled = this.pageIndex <= 1;
        if(nextBtn) nextBtn.disabled = this.pageIndex >= total;
    },
    
    /**
     * Initialise le scroll infini pour mobile
     */
    _initInfiniteScroll(){
        if(typeof InfiniteScroll === 'undefined') return;
        
        const tbody = document.getElementById('mvBody');
        const pager = document.querySelector('#pgMovements .pager, #pgMovements .pagination-bar');
        
        if(!tbody) return;
        
        // Ajouter classe pour cacher le pager sur mobile
        if(pager) pager.classList.add('hide-mobile');
        
        InfiniteScroll.init({
            id: 'movements',
            container: document.querySelector('.main-content') || window,
            target: tbody,
            pagerEl: pager,
            pageSize: this.pageSize || 20,
            threshold: 300,
            loadMore: async (page, size) => {
                const start = (page - 1) * size;
                const end = start + size;
                const items = (this._filtered || []).slice(start, end);
                
                return {
                    items: items,
                    hasMore: end < (this._filtered || []).length
                };
            },
            renderItems: (items, append) => {
                this._renderTableRows(items, append);
            }
        });
    },
    
    /**
     * Render les lignes du tableau avec support append
     */

// Regroupe les entrées issues d'un même BC en une ligne déroulable (ergonomie)
_groupForRender(mvs) {
    const getBcId = (m) => {
        const bcMatch = (m.reason || '').match(/\bBC-\d{8}-[A-Z0-9]+\b/);
        return bcMatch ? bcMatch[0] : '';
    };

    const groups = new Map();
    const singles = [];

    for (const m of mvs) {
        const bcId = getBcId(m);
        const isEntry = (m.type === 'entry');
        const isReception = (m.reason || '').toLowerCase().includes('réception');
        if (bcId && isEntry && isReception) {
            if (!groups.has(bcId)) {
                groups.set(bcId, {
                    __group: true,
                    id: `grp_${bcId}`,
                    bcId,
                    date: m.date,
                    type: 'entry',
                    items: []
                });
            }
            const g = groups.get(bcId);
            g.items.push(m);
            if (m.date && g.date && m.date > g.date) g.date = m.date;
        } else {
            singles.push(m);
        }
    }

    const grouped = [...groups.values()].map(g => {
        g.quantity = g.items.reduce((s, it) => s + (Number(it.quantity) || 0), 0);
        g.reason = `Réception BC ${g.bcId}`;
        return g;
    });

    const all = [...singles, ...grouped];
    all.sort((a,b) => (b.date || '').localeCompare(a.date || ''));
    return all;
},

_renderGroupBlock(g) {
    const date = g.date ? Locale.formatDateTime(g.date) : '—';

    const qty = Number(g.quantity) || 0;
    const qtyDisplay = `<span class="text-success">+${Math.abs(qty)}</span>`;

    const docLabel = g.bcId || '—';
    const bcUrl = g.bcId ? ('orders?open=' + encodeURIComponent(g.bcId)) : '';
    const docBtn = g.bcId
        ? `<button class="btn btn-xs btn-primary" title="Ouvrir le bon de commande"
            onclick="window.open('${bcUrl}','_blank')">📦 ${g.bcId}</button>`
        : '—';

    const count = g.items ? g.items.length : 0;

    const detailsRows = (g.items || []).map(m => {
        const p = this.prods.find(x=>x.id===m.productId);
        const w = this.whs.find(x=>x.id==m.warehouseId);
        const currentStock = this._getCurrentStock(m.productId, m.warehouseId);
        const qtySign2 = m.type === 'remove' ? '-' : '+';
        const qtyClass2 = m.type === 'remove' ? 'text-danger' : 'text-success';
        const qtyDisplay2 = `<span class="${qtyClass2}">${qtySign2}${Math.abs(m.quantity)}</span>`;
        return `
            <tr>
                <td>${p?.name || '—'}</td>
                <td>${p?.sku || '—'}</td>
                <td>${w?.name || '—'}</td>
                <td>${qtyDisplay2}</td>
                <td>${currentStock}</td>
            </tr>
        `;
    }).join('');

    return `
        <tr class="movement-group" data-group="${g.id}">
            <td class="cell-center mv-select-cell">—</td>
            <td>
                <button class="btn btn-xs btn-secondary" title="Dérouler / replier"
                    onclick="MvPage._toggleGroup('${g.id}')">▸</button>
                <span style="margin-left:6px">${date}</span>
            </td>
            <td><span class="badge badge-success">ENTRÉE</span></td>
            <td><strong>Réception ${g.bcId}</strong><div class="muted">${count} ligne(s)</div></td>
            <td>—</td>
            <td>—</td>
            <td>${qtyDisplay}</td>
            <td>—</td>
            <td>${g.reason}</td>
            <td>—</td>
            <td>—</td>
            <td>${docLabel}</td>
            <td class="cell-center">${docBtn}</td>
        </tr>
        <tr class="movement-group-details" data-group-details="${g.id}" style="display:none">
            <td colspan="13" class="group-details-cell">
                <div class="group-details-wrap">
                    <table class="table table-sm">
                        <thead>
                            <tr>
                                <th>Produit</th>
                                <th>SKU</th>
                                <th>Magasin</th>
                                <th>Qté</th>
                                <th>Stock</th>
                            </tr>
                        </thead>
                        <tbody>${detailsRows}</tbody>
                    </table>
                </div>
            </td>
        </tr>
    `;
},

_toggleGroup(groupId) {
    const details = document.querySelector(`tr[data-group-details="${groupId}"]`);
    const btn = document.querySelector(`tr[data-group="${groupId}"] button`);
    if (!details) return;
    const open = details.style.display !== 'none';
    details.style.display = open ? 'none' : '';
    if (btn) btn.textContent = open ? '▸' : '▾';
},

    _renderTableRows(mvs, append = false){
        const tbody = document.getElementById('mvBody');
        if(!tbody) return;
        
        if(!mvs.length && !append){
            tbody.innerHTML = '<tr><td colspan="13" class="tbl-empty">Aucun mouvement</td></tr>';
            return;
        }
        
        const typeLabel = {
            add: 'AJOUT',
            remove: 'SORTIE', 
            transfer: 'TRANSFERT',
            entry: 'ENTRÉE',
            inventory: 'INVENTAIRE'
        };
        const typeCls = {
            add: 'success',
            remove: 'danger',
            transfer: 'info',
            entry: 'success',
            inventory: 'warning'
        };
        
        const html = mvs.map(m=>{
            
            if (m && m.__group) return this._renderGroupBlock(m);
const p = this.prods.find(x=>x.id===m.productId);
            const w = this.whs.find(x=>x.id==m.warehouseId);
            const date = Locale.formatDateTime(m.date);
            
            const qtySign = m.type === 'remove' ? '-' : '+';
            const qtyClass = m.type === 'remove' ? 'text-danger' : 'text-success';
            const qtyDisplay = `<span class="${qtyClass}">${qtySign}${Math.abs(m.quantity)}</span>`;
            const currentStock = this._getCurrentStock(m.productId, m.warehouseId);
            const reasonSite = [m.reason, m.site].filter(Boolean).join(' · ') || '—';
            
            const bcMatch = (m.reason || '').match(/\bBC-\d{8}-[A-Z0-9]+\b/);
            const bcId = bcMatch ? bcMatch[0] : '';
            const docLabel = bcId || '—';
            let docBtn = '—';
            if (bcId) {
                const bcUrl = 'orders?open=' + encodeURIComponent(bcId);
                docBtn = `<button class="btn btn-xs btn-primary" title="Ouvrir le bon de commande" onclick="window.open('${bcUrl}','_blank')">📦 ${bcId}</button>`;
            } else if (['remove','transfer'].includes(m.type)) {
                docBtn = `<button class="btn btn-xs btn-outline-secondary mv-btn-bl" title="Bon de livraison individuel" onclick="window.open('${Api.url(`api/delivery_note.php?movementId=${m.id}`)}','_blank')">BL</button>`;
            }

            const selectedRowClass = this._selectedDeliveryMoves?.has(String(m.id || '')) ? ' class="is-delivery-selected"' : '';
            return `
                <tr${selectedRowClass} data-movement-id="${this._escapeHtml(m.id || '')}">
                    <td class="cell-center mv-select-cell" data-label="BL">${this._renderDeliveryCheckbox(m)}</td>
                    <td data-label="Date">${date}</td>
                    <td data-label="Type"><span class="badge badge-${typeCls[m.type]||'secondary'}">${typeLabel[m.type]||m.type}</span></td>
                    <td data-label="Produit"><strong>${p?.name||'?'}</strong></td>
                    <td class="col-mono" data-label="SKU">${p?.sku||'—'}</td>
                    <td data-label="Magasin">${w?.name||'?'}</td>
                    <td class="col-qty" data-label="Quantité">${qtyDisplay}</td>
                    <td class="col-stock" data-label="Stock">${currentStock}</td>
                    <td data-label="Raison / chantier">${reasonSite}</td>
                    <td data-label="Opérateur">${m.operator||'—'}</td>
                    <td data-label="Note">${m.note||'—'}</td>
                    <td data-label="Document">${docLabel}</td>
                    <td class="cell-center" data-label="Pièce">${docBtn}</td>
                </tr>
            `;
        }).join('');
        
        if(append){
            tbody.insertAdjacentHTML('beforeend', html);
        } else {
            tbody.innerHTML = html;
        }
        this.updateDeliverySelectionBar();
    },

    // ================================================================
    // ================================================================
    // MODAL MOUVEMENT — Cascade Magasin → Produit → Détails
    // v2.3.15 — UX logique : on choisit le magasin d'abord,
    // la liste produits se filtre en temps réel, le stock courant
    // est affiché comme contexte. Recherche live incluse.
    // ================================================================
    openMvModal(type, prefilledProduct = null, prefilledWarehouse = null) {
        const lbls = {
            add:      'Ajout de stock',
            remove:   'Sortie de stock',
            transfer: 'Transfert de stock',
            entry:    'Entrée de stock'
        };
        const icn = { add:'success', remove:'error', transfer:'info', entry:'success' };
        this._mvType = type;

        // ── Unités ──────────────────────────────────────────────────
        const unitLabel = u => ({
            piece:'pièce', ml:'mètre linéaire', m2:'m²', m3:'m³',
            litre:'litre', kg:'kg', fut:'fût', aerosol:'aérosol',
            boite:'boîte', carton:'carton', palette:'palette',
            bidon:'bidon', sac:'sac', seau:'seau', rouleau:'rouleau',
            bobine:'bobine', tube:'tube', paquet:'paquet',
            m:'mètre'
        }[u] || u);
        const allUnits = ['piece','carton','palette','bidon','fut','aerosol',
            'sac','seau','rouleau','bobine','tube','boite','paquet',
            'ml','litre','kg','m','m2','m3'];
        const unitOpts = allUnits.map(u=>`<option value="${u}">${unitLabel(u)}</option>`).join('');

        // ── HTML du formulaire ───────────────────────────────────────
        const whOpts = this.whs.map(w=>
            `<option value="${w.id}">${w.name}</option>`
        ).join('');
        const suppOpts = (this.suppliers||[]).map(s=>
            `<option value="${s.name}">${s.name}</option>`
        ).join('');

        // Scanner block (entry only)
        const scannerBlock = type === 'entry' ? `
            <div class="mv-scanner-zone">
                <div class="mv-scanner-label"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7V4h3M17 4h3v3M20 17v3h-3M7 20H4v-3"></path><path d="M7 12h10"></path></svg><span>Scanner code-barres</span></div>
                <div class="mv-scanner-row">
                    <input type="text" class="inp" id="mvmScanner"
                        placeholder="Scannez ou saisissez le code-barres…" autocomplete="off">
                    <button type="button" class="btn btn-secondary" id="mvmScanCamera"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"></path><circle cx="12" cy="13" r="4"></circle></svg><span>Caméra</span></button>
                </div>
                <small class="mv-scanner-hint">Le scan pré-remplit le produit automatiquement.</small>
            </div>` : '';

        // Destination (transfer only)
        const transferDst = type === 'transfer' ? `
            <div class="mv-field-group">
                <label class="lbl">Magasin destination *</label>
                <select class="sel" id="mvmWhDst">
                    <option value="">— Choisir —</option>${whOpts}
                </select>
            </div>` : '';

        // Fournisseur (entry only)
        const supplierBlock = type === 'entry' ? `
            <div class="mv-field-group">
                <label class="lbl">Fournisseur *</label>
                <select class="sel" id="mvmSupplier">
                    <option value="">— Choisir —</option>${suppOpts}
                </select>
            </div>` : '';

        // Raison (remove only)
        const removeBlock = type === 'remove' ? `
            <div class="mv-section-sep"></div>
            <div class="mv-section-title">📋 Destination</div>
            <div class="mv-field-group">
                <label class="lbl">Raison / Destination</label>
                <select class="sel" id="mvmReason">
                    <option value="Chantier">Chantier</option>
                    <option value="Réparation">Réparation</option>
                    <option value="Livraison client">Livraison client</option>
                    <option value="Consommable">Consommable</option>
                    <option value="Autre">Autre</option>
                </select>
            </div>
            <div class="mv-field-group">
                <label class="lbl">Chantier / Lieu</label>
                <input type="text" class="inp" id="mvmSite" placeholder="Ex: Chantier Rue Dupont…">
            </div>
            <div class="mv-field-group">
                <label class="lbl">Nom opérateur</label>
                <input type="text" class="inp" id="mvmOper" placeholder="Nom prénom…">
            </div>` : '';

        // Prix achat (add) / coût unitaire (entry)
        const costBlock = type === 'add' ? `
            <div class="mv-field-group">
                <label class="lbl">Prix d'achat (pour l'unité sélectionnée)</label>
                <input type="number" class="inp" id="mvmCost" min="0" step="0.0001" placeholder="Ex: 12.50">
            </div>` :
            type === 'entry' ? `
            <div class="mv-field-group">
                <label class="lbl">Coût unitaire (${Locale.currencyCode()})</label>
                <input type="number" class="inp" id="mvmUnitCost" min="0" step="0.01" value="0">
            </div>` : '';

        // BL (entry only)
        const blBlock = type === 'entry' ? `
            <div class="mv-field-group">
                <label class="lbl">BL / Référence</label>
                <input type="text" class="inp" id="mvmDeliveryNote" placeholder="Ex: BL-2026-001">
            </div>` : '';

        // Note (add / transfer)
        const noteBlock = (type === 'add' || type === 'transfer') ? `
            <div class="mv-field-group">
                <label class="lbl">Note</label>
                <input type="text" class="inp" id="mvmNote">
            </div>` : '';

        const html = `
<div class="mv-modal-form">

${scannerBlock}

    <!-- ① MAGASIN (toujours en premier) -->
    <div class="mv-step" id="mvStepWh">
        <div class="mv-step-header">
            <span class="mv-step-num">1</span>
            <span class="mv-step-label">Magasin ${type==='transfer'?'source':''} *</span>
        </div>
        <select class="sel" id="mvmWh">
            <option value="">— Choisir un magasin —</option>
            ${whOpts}
        </select>
    </div>

    ${transferDst ? `<div class="mv-step">${transferDst}</div>` : ''}

    <!-- ② PRODUIT (filtré + recherche, activé après magasin) -->
    <div class="mv-step mv-step-locked" id="mvStepProd">
        <div class="mv-step-header">
            <span class="mv-step-num">2</span>
            <span class="mv-step-label">Produit *</span>
            <span class="mv-stock-badge" id="mvStockBadge" style="display:none"></span>
        </div>
        <!-- Recherche texte -->
        <div class="mv-prod-search-wrap">
            <input type="text" class="inp mv-prod-search" id="mvmProdSearch"
                placeholder="Rechercher un produit…" autocomplete="off" disabled>
        </div>
        <!-- Liste déroulante filtrée -->
        <select class="sel" id="mvmProd" disabled>
            <option value="">— Choisir d'abord un magasin —</option>
        </select>
    </div>

    ${supplierBlock}

    <!-- ③ QUANTITÉ & CONDITIONNEMENT (activé après produit) -->
    <div class="mv-step mv-step-locked" id="mvStepQty">
        <div class="mv-step-header">
            <span class="mv-step-num">3</span>
            <span class="mv-step-label">Quantité & Conditionnement</span>
        </div>
        <div class="mv-qty-row">
            <div class="mv-qty-unit">
                <label class="lbl">Unité</label>
                <select class="sel" id="mvmUnit" disabled>${unitOpts}</select>
            </div>
            <div class="mv-qty-num">
                <label class="lbl">Quantité *</label>
                <input type="number" class="inp" id="mvmQty" min="0.01" step="0.01" value="1" disabled>
            </div>
        </div>
        <small class="mv-conv-hint">Conversion automatique vers l'unité de base du produit lors de l'enregistrement.</small>
        ${costBlock}
    </div>

    <!-- ④ TRAÇABILITÉ -->
    <div class="mv-step mv-step-locked" id="mvStepTrace">
        <div class="mv-step-header">
            <span class="mv-step-num">4</span>
            <span class="mv-step-label">Traçabilité</span>
        </div>
        <div class="mv-field-group">
            <label class="lbl">Numéro de lot</label>
            <input type="text" class="inp" id="mvmLot" placeholder="Ex: LOT-2026-001" disabled>
        </div>
        <div class="mv-field-group">
            <label class="lbl">DLUO / Péremption</label>
            <input type="date" class="inp" id="mvmExpiry" disabled>
        </div>
        ${blBlock}
        ${removeBlock}
        ${noteBlock}
    </div>

</div>`;

        Modal.show(lbls[type], html, [
            { label:'Annuler', cls:'btn-secondary', onclick:()=>Modal.close() },
            { label:'Valider', cls:'btn-'+(type==='remove'?'danger':'primary'), onclick:()=>this.submitMv() }
        ], icn[type]);

        // ── Injecter le CSS du formulaire (une seule fois) ───────────
        this._injectMvModalCss();

        // ── Prefill si contexte ──────────────────────────────────────
        if (prefilledWarehouse) {
            const whSel = document.getElementById('mvmWh');
            if (whSel) {
                whSel.value = prefilledWarehouse;
                this._onWhChange(type, prefilledProduct);
            }
        }

        // ── Cascade Magasin → Produit ────────────────────────────────
        const whSel = document.getElementById('mvmWh');
        if (whSel) whSel.addEventListener('change', () => {
            this._onWhChange(type, null);
        });

        // ── Scanner entry ────────────────────────────────────────────
        if (type === 'entry') {
            const scanInp = document.getElementById('mvmScanner');
            if (scanInp) {
                setTimeout(() => scanInp.focus(), 80);
                scanInp.addEventListener('keypress', async e => {
                    if (e.key === 'Enter') {
                        e.preventDefault();
                        const v = scanInp.value.trim();
                        if (v) await this.handleBarcodeScan(v);
                    }
                });
            }
            const scanBtn = document.getElementById('mvmScanCamera');
            if (scanBtn) scanBtn.onclick = () => this._openCameraModal();
        }
    },

    // ─────────────────────────────────────────────────────────────
    // Cascade : changement de magasin
    // ─────────────────────────────────────────────────────────────
    _onWhChange(type, prefillPid) {
        const whSel  = document.getElementById('mvmWh');
        const prodSel = document.getElementById('mvmProd');
        const search  = document.getElementById('mvmProdSearch');
        const stepProd = document.getElementById('mvStepProd');
        const stepQty  = document.getElementById('mvStepQty');
        const stepTrace = document.getElementById('mvStepTrace');
        const badge   = document.getElementById('mvStockBadge');

        const wid = whSel ? whSel.value : '';
        if (!wid) return;

        // Débloquer l'étape produit
        if (stepProd) stepProd.classList.remove('mv-step-locked');
        if (prodSel)  prodSel.disabled = false;
        if (search)   search.disabled = false;

        const widInt = parseInt(wid);

        // ── Correspondance produit ↔ magasin ─────────────────────
        // Un produit n'est proposé dans un magasin que s'il possède une ligne stock
        // pour ce magasin. allowedWarehouses reste une règle d'autorisation, mais ne
        // suffit plus à afficher le produit comme présent/intégré dans le magasin.
        const widStr = String(wid);
        const _isAllowed = (p) => {
            if(window.StockCascade) return StockCascade.productInWarehouse(p, widStr, { includeUnrestricted: true });
            let allowed = p.allowedWarehouses;
            if (typeof allowed === 'string') {
                try { allowed = JSON.parse(allowed || '[]'); } catch(e) { allowed = []; }
            }
            if (!Array.isArray(allowed) || allowed.length === 0) return true;
            return allowed.some(a => String(a) === widStr);
        };

        const stockRowsInWh = (this.stock || [])
            .filter(s => String(s.warehouseId) === String(wid))
            .map(s => String(s.productId));

        const stockPositiveInWh = (this.stock || [])
            .filter(s => String(s.warehouseId) === String(wid) && (Number(s.quantity)||0) > 0)
            .map(s => String(s.productId));

        // ── Produits disponibles selon le type ───────────────────
        let availableProds;
        if (type === 'remove' || type === 'transfer') {
            // Seulement ceux avec du stock positif ET autorisés dans ce magasin
            availableProds = this.prods.filter(p =>
                stockPositiveInWh.includes(String(p.id)) && _isAllowed(p)
            );
        } else {
            // add/entry : produits déjà intégrés au magasin, même avec stock à 0
            availableProds = this.prods.filter(p =>
                stockRowsInWh.includes(String(p.id)) && _isAllowed(p)
            );
        }

        this._mvAvailableProds = availableProds;
        this._renderProdOptions(availableProds, prefillPid);

        // ── Bind recherche avec auto-sélection ───────────────────
        if (search) {
            search.value = '';
            search.oninput = () => {
                const q = search.value.toLowerCase().trim();
                const filtered = q
                    ? availableProds.filter(p =>
                        (p.name||'').toLowerCase().includes(q) ||
                        (p.sku||'').toLowerCase().includes(q) ||
                        (p.barcode||'').toLowerCase().includes(q)
                      )
                    : availableProds;
                this._renderProdOptions(filtered, null);

                // Auto-sélection si un seul résultat
                if (filtered.length === 1) {
                    const prodSel2 = document.getElementById('mvmProd');
                    if (prodSel2) {
                        prodSel2.value = filtered[0].id;
                        this._onProdChange(type);
                    }
                }
            };
            // Auto-sélection aussi sur Enter dans la recherche
            search.onkeydown = (e) => {
                if (e.key === 'Enter') {
                    e.preventDefault();
                    const prodSel2 = document.getElementById('mvmProd');
                    if (prodSel2 && prodSel2.options.length === 2) { // 1 option + placeholder
                        prodSel2.selectedIndex = 1;
                        this._onProdChange(type);
                    }
                }
            };
        }

        // Bind changement produit
        if (prodSel) {
            prodSel.onchange = () => this._onProdChange(type);
            if (prefillPid) {
                prodSel.value = prefillPid;
                this._onProdChange(type);
            }
        }

        // Reset étapes suivantes
        if (stepQty)   stepQty.classList.add('mv-step-locked');
        if (stepTrace) stepTrace.classList.add('mv-step-locked');
        if (badge) badge.style.display = 'none';

        ['mvmUnit','mvmQty','mvmLot','mvmExpiry'].forEach(id => {
            const el = document.getElementById(id);
            if (el) el.disabled = true;
        });
    },

    _renderProdOptions(prods, prefillPid) {
        const prodSel = document.getElementById('mvmProd');
        if (!prodSel) return;
        if (!prods.length) {
            prodSel.innerHTML = '<option value="">Aucun produit disponible</option>';
            return;
        }
        prodSel.innerHTML = '<option value="">— Choisir un produit —</option>' +
            prods.map(p => {
                const sku = p.sku ? ` — ${p.sku}` : '';
                const sel = prefillPid && String(p.id) === String(prefillPid) ? ' selected' : '';
                return `<option value="${p.id}"${sel}>${p.name}${sku}</option>`;
            }).join('');
    },

    // ─────────────────────────────────────────────────────────────
    // Cascade : changement de produit
    // ─────────────────────────────────────────────────────────────
    _onProdChange(type) {
        const prodSel  = document.getElementById('mvmProd');
        const unitSel  = document.getElementById('mvmUnit');
        const qtyInp   = document.getElementById('mvmQty');
        const lotInp   = document.getElementById('mvmLot');
        const expInp   = document.getElementById('mvmExpiry');
        const stepQty  = document.getElementById('mvStepQty');
        const stepTrace = document.getElementById('mvStepTrace');
        const badge    = document.getElementById('mvStockBadge');
        const whSel    = document.getElementById('mvmWh');

        const pid = prodSel ? prodSel.value : '';
        if (!pid) return;

        const p   = this.prods.find(x => String(x.id) === String(pid));
        const wid = whSel ? whSel.value : '';
        if (!p) return;

        // Débloquer étapes Qty et Trace
        if (stepQty)   stepQty.classList.remove('mv-step-locked');
        if (stepTrace) stepTrace.classList.remove('mv-step-locked');

        ['mvmUnit','mvmQty','mvmLot','mvmExpiry'].forEach(id => {
            const el = document.getElementById(id);
            if (el) el.disabled = false;
        });

        // Unité de base du produit
        const base = (p.packBaseUnit || p.unitType || 'piece');
        if (unitSel && [...unitSel.options].some(o => o.value === base)) {
            unitSel.value = base;
        }

        // Obligations traçabilité
        if (lotInp) {
            lotInp.placeholder = p.trackLot
                ? 'OBLIGATOIRE — ex: LOT-2026-001'
                : 'Ex: LOT-2026-001';
        }
        if (expInp) expInp.required = !!p.trackExpiry;

        // Badge stock actuel dans ce magasin
        if (badge && wid) {
            const stockEntry = (this.stock||[]).find(s =>
                String(s.productId) === String(pid) &&
                String(s.warehouseId) === String(wid)
            );
            const qty = stockEntry ? (Number(stockEntry.quantity)||0) : 0;
            badge.style.display = '';
            if (type === 'remove' || type === 'transfer') {
                badge.className = `mv-stock-badge ${qty <= 0 ? 'mv-stock-zero' : qty < (p.minStock||5) ? 'mv-stock-low' : 'mv-stock-ok'}`;
                badge.textContent = `Stock : ${qty} ${base}`;
            } else {
                badge.className = 'mv-stock-badge mv-stock-ok';
                badge.textContent = `Stock actuel : ${qty} ${base}`;
            }
        }

        // Focus quantité
        setTimeout(() => { if (qtyInp) qtyInp.focus(); }, 60);
    },

    // ─────────────────────────────────────────────────────────────
    // CSS injecté dynamiquement (formulaire modal)
    // ─────────────────────────────────────────────────────────────
    _injectMvModalCss() {
        if (document.getElementById('mv-modal-css')) return;
        const style = document.createElement('style');
        style.id = 'mv-modal-css';
        style.textContent = `
/* ── Scanner zone ────────────────────────────────────────── */
.mv-scanner-zone {
    background: var(--blue-pale, #eff6ff);
    border: 2px dashed var(--blue, #3b82f6);
    border-radius: var(--r, 8px);
    padding: 14px;
    margin-bottom: 4px;
}
.mv-scanner-label {
    display: flex;
    align-items: center;
    gap: 7px;
    font-weight: 700;
    font-size: 13px;
    color: var(--blue, #3b82f6);
    margin-bottom: 8px;
}
.mv-scanner-row {
    display: flex;
    gap: 8px;
    align-items: center;
}
.mv-scanner-row .inp { flex: 1; }
.mv-scanner-label svg, #mvmScanCamera svg { width: 19px; height: 19px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
#mvmScanCamera { display: inline-flex; align-items: center; justify-content: center; gap: 7px; }
.mv-scanner-hint {
    display: block;
    margin-top: 6px;
    font-size: 12px;
    color: var(--text-muted);
}

/* ── Steps ───────────────────────────────────────────────── */
.mv-modal-form {
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.mv-step {
    border: 1px solid var(--border);
    border-radius: var(--r-lg, 10px);
    padding: 14px 16px;
    transition: opacity .2s, background .2s;
    background: var(--bg-card);
}
.mv-step-locked {
    opacity: .45;
    pointer-events: none;
    background: var(--bg-elevated);
}
.mv-step-header {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 10px;
    flex-wrap: wrap;
}
.mv-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;
}
.mv-step-locked .mv-step-num {
    background: var(--text-muted);
}
.mv-step-label {
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary);
    flex: 1;
}

/* ── Stock badge ─────────────────────────────────────────── */
.mv-stock-badge {
    font-size: 12px;
    font-weight: 600;
    padding: 3px 10px;
    border-radius: 999px;
}
.mv-stock-ok   { background: var(--green-pale, #dcfce7); color: var(--green, #16a34a); }
.mv-stock-low  { background: var(--yellow-pale, #fef9c3); color: #b45309; }
.mv-stock-zero { background: var(--red-pale, #fee2e2); color: var(--red, #dc2626); }

/* ── Recherche produit ───────────────────────────────────── */
.mv-prod-search-wrap { margin-bottom: 8px; }
.mv-prod-search { width: 100%; }

/* ── Qty row ─────────────────────────────────────────────── */
.mv-qty-row {
    display: grid;
    grid-template-columns: 1fr 2fr;
    gap: 12px;
}
@media (max-width: 420px) {
    .mv-qty-row { grid-template-columns: 1fr; }
}

/* ── Misc ────────────────────────────────────────────────── */
.mv-conv-hint {
    display: block;
    font-size: 12px;
    color: var(--text-muted);
    margin-top: 6px;
    margin-bottom: 8px;
}
.mv-section-sep {
    height: 1px;
    background: var(--border);
    margin: 8px 0;
}
.mv-section-title {
    font-size: 13px;
    font-weight: 700;
    color: var(--text-secondary);
    margin-bottom: 8px;
}
.mv-field-group {
    margin-top: 10px;
}
.mv-field-group .lbl {
    display: block;
    margin-bottom: 4px;
}
        `;
        document.head.appendChild(style);
    },

    // ─────────────────────────────────────────────────────────────
    // Scan code-barres : remplit le produit dans le formulaire
    // ─────────────────────────────────────────────────────────────
    async handleBarcodeScan(value) {
        const scanInp = document.getElementById('mvmScanner');
        const prodSel = document.getElementById('mvmProd');
        let result = value && typeof value === 'object' && value.status ? value : null;
        const feedbackHandledByConsumer = !!result;
        let product = value && typeof value === 'object' && value.id != null && !value.status ? value : result?.product;
        const raw = result?.raw || (typeof value === 'string' || typeof value === 'number' ? String(value).trim() : '');

        if (!product && raw && window.MageStockScanner?.resolve) {
            const resolverOptions = { products: this.prods || [], context: 'movement' };
            const warehouseId = document.getElementById('mvmWh')?.value || '';
            if (warehouseId && prodSel) {
                resolverOptions.allowedProductIds = [...prodSel.options]
                    .map(option => option.value)
                    .filter(Boolean);
            }
            result = await window.MageStockScanner.resolve(raw, resolverOptions);
            product = result?.product || null;
        }
        if (!product && raw) {
            product = (this.prods || []).find(item => {
                if (String(item.barcode || '') === raw || String(item.sku || '') === raw) return true;
                return (Array.isArray(item.eans) ? item.eans : []).some(ean =>
                    String(typeof ean === 'object' ? (ean.code || ean.value || '') : ean) === raw
                );
            }) || null;
        }

        if (!product) {
            if (!feedbackHandledByConsumer) this.playErrorSound();
            if (result?.status === 'out_of_scope') toast('Ce produit existe mais n’est pas disponible dans le magasin sélectionné.', 'error');
            else if (result?.status === 'ambiguous') toast('Plusieurs produits correspondent à ce code. Utilisez le scanner pour choisir le bon.', 'error');
            else toast(`Code introuvable${raw ? ` : ${raw}` : ''}`, 'error');
            if (scanInp) scanInp.value = '';
            return null;
        }

        if (prodSel && [...prodSel.options].some(option => String(option.value) === String(product.id))) {
            prodSel.value = String(product.id);
            this._onProdChange(this._mvType);
        } else {
            this._pendingBarcodePid = product.id;
            toast(`✅ ${product.name} — choisissez d’abord un magasin`, 'info');
        }

        if (!feedbackHandledByConsumer) this.playSuccessSound();
        if (scanInp) scanInp.value = '';
        toast(`✅ ${product.name}`, 'success');
        return product;
    },

    _movementScannerOptions() {
        const productSelect = document.getElementById('mvmProd');
        const warehouseId = document.getElementById('mvmWh')?.value || '';
        const resolverOptions = { products: this.prods || [] };
        if (warehouseId && productSelect) {
            resolverOptions.allowedProductIds = [...productSelect.options]
                .map(option => option.value)
                .filter(Boolean);
        }
        return resolverOptions;
    },

    // Ouvre le composant scanner commun. Les règles métier restent dans handleBarcodeScan().
    _openCameraModal(trigger) {
        if (!window.MageStockScannerConsumer?.open) {
            toast('Composant scanner indisponible. Utilisez la saisie manuelle.', 'error');
            document.getElementById('mvmScanner')?.focus?.();
            return null;
        }
        if (this._movementScannerConsumer && !this._movementScannerConsumer.closed) return this._movementScannerConsumer;

        this._movementScannerConsumer = window.MageStockScannerConsumer.open({
            title: 'Scanner un produit',
            description: 'Le résultat sera ajouté au mouvement en cours.',
            context: 'movement',
            owner: 'movement-form',
            mode: 'auto',
            trigger: trigger || document.getElementById('mvmScanCamera'),
            resolverOptions: () => this._movementScannerOptions(),
            onResolved: async (result, handle) => {
                await handle.stop('movement-resolved');
                this._movementScannerConsumer = null;
                handle.close('resolved');
                await this.handleBarcodeScan(result);
                return false;
            },
            onOutOfScope: result => {
                toast(`${result?.product?.name || 'Ce produit'} n’est pas disponible dans le magasin sélectionné.`, 'error');
            },
            onNotFound: result => {
                toast(`Code introuvable${result?.raw ? ` : ${result.raw}` : ''}`, 'error');
            },
            onClose: () => { this._movementScannerConsumer = null; },
            onError: error => console.error('[Movements scanner]', error)
        });
        return this._movementScannerConsumer;
    },

    // ======== SOUNDS ========
    playSuccessSound() {
        return window.MageStockScannerFeedback?.success?.() || false;
    },
    playErrorSound() {
        return window.MageStockScannerFeedback?.error?.() || false;
    },
    async submitMv(){
        const type = this._mvType;
        const pid  = document.getElementById('mvmProd').value;
        const wid  = document.getElementById('mvmWh').value;
        const qty  = parseFloat(document.getElementById('mvmQty').value)||0;
        const unit = document.getElementById('mvmUnit')?.value || 'piece';

        if(!pid || !wid || qty<=0){
            toast('Produit, magasin et quantité obligatoires', 'error');
            return;
        }

        if(type === 'entry') {
            const supplier = document.getElementById('mvmSupplier')?.value;
            if(!supplier) { toast('Fournisseur obligatoire pour les entrées', 'error'); return; }
        }

        const p = this.prods.find(x=>String(x.id)===String(pid));
        if(!p) return toast('Produit introuvable','error');

        const lot = (document.getElementById('mvmLot')?.value||'').trim();
        if(p.trackLot && !lot){ toast('Numéro de lot obligatoire pour ce produit','error'); return; }

        const expiry = document.getElementById('mvmExpiry')?.value || '';
        if(p.trackExpiry && !expiry){ toast('Date de péremption obligatoire pour ce produit','error'); return; }

        if(type==='transfer'){
            const wdst = document.getElementById('mvmWhDst')?.value;
            if(!wdst || wdst===wid){ toast('Magasin destination requis et différent de la source','error'); return; }
        }

        const data = {
            type,
            productId: parseInt(pid),
            warehouseId: parseInt(wid),
            quantity: (type==='remove' ? -Math.abs(qty) : Math.abs(qty)),
            unit,
            lotNumber: lot || null,
            expiryDate: expiry || null
        };

        if(type==='entry'){
            data.unitCost      = parseFloat(document.getElementById('mvmUnitCost')?.value) || 0;
            data.deliveryNote  = document.getElementById('mvmDeliveryNote')?.value?.trim() || '';
            data.supplier      = document.getElementById('mvmSupplier')?.value || '';
        }
        if(type==='add'){
            const cost = parseFloat(document.getElementById('mvmCost')?.value) || 0;
            if(cost > 0) data.unitCost = cost;
            data.notes = document.getElementById('mvmNote')?.value?.trim() || '';
        }
        if(type==='remove'){
            const reason = document.getElementById('mvmReason')?.value || '';
            const site   = document.getElementById('mvmSite')?.value?.trim() || '';
            const oper   = document.getElementById('mvmOper')?.value?.trim() || '';
            data.notes   = [reason, site, oper].filter(Boolean).join(' — ');
        }
        if(type==='transfer'){
            data.warehouseIdDest = parseInt(document.getElementById('mvmWhDst').value);
            data.notes = document.getElementById('mvmNote')?.value?.trim() || '';
        }

        try {
            await Api.mvAdd(data);
            toast('Mouvement enregistré','success');
            Modal.close();
            await this.load();
        } catch(e){
            toast(e.message,'error');
        }
    },

    async startCameraScan() {
        return this._openCameraModal(document.getElementById('mvmScanCamera'));
    },

    stopCameraScan() {
        const handle = this._movementScannerConsumer;
        this._movementScannerConsumer = null;
        handle?.close?.('movement-stop');
        return window.MageStockScannerConsumer?.stop?.('movement-stop');
    },


async loadQuagga() {
        return new Promise((resolve, reject) => {
            if(typeof Quagga !== 'undefined') {
                resolve();
                return;
            }
            
            const script = document.createElement('script');
            script.src = 'https://cdn.jsdelivr.net/npm/@ericblade/quagga2/dist/quagga.min.js';
            script.onload = () => resolve();
            script.onerror = () => reject(new Error('Failed to load Quagga2'));
            document.head.appendChild(script);
        });
    }
};
