/**
 * Fichier  : assets/js/pages/barcodes.js
 * Auteur   : KREMER Régis
 * Version  : 2.3.1
 * Date     : 2026-03-14
 * Description : JS — Page codes-barres : génération, impression, batch
 *
 * Changelog :
 *   v2.1.0 — Version initiale
 *   v2.2.0 — Permissions magasins, bons de commande
 *   v2.3.0 — Refonte preview GRID, structure étiquette unifiée,
 *            checkbox accessible, print/PDF identiques à la preview
 *   v2.3.1 — Ajout filtre magasin + catégories liées au magasin sur la sélection produits
 */
// ================================================================
// barcodes.js — Générateur d'étiquettes COMPLET (refonte v2.3)
// ================================================================

const BcPage = {
    prods: [],
    selected: new Set(),
    style: 'img_barcode',
    format: 'A4_24',

    formats: {
        A4_24:      { cols: 3, rows: 8, wmm: 70,  hmm: 37, gapmm: 2.5, title: 'A4 — planche 24' },
        Avery_L7160:{ cols: 3, rows: 7, wmm: 63.5,hmm: 38.1, gapmm: 2.5, title: 'Avery L7160' },
        '70x37':    { cols: 3, rows: 8, wmm: 70,  hmm: 37, gapmm: 2.5, title: '70×37mm' },
        '105x37':   { cols: 2, rows: 8, wmm: 105, hmm: 37, gapmm: 3.0, title: '105×37mm' },
    },

    // ══════════════════════════════════════════════════════════════
    // INITIALISATION
    // ══════════════════════════════════════════════════════════════
    init: async function(){
        this.prods = await Api.products();

        const whs = await Api.warehouses();
        this.whs = Array.isArray(whs) ? whs : [];
        this.currentWarehouseId = '';

        this._populateWarehouseFilter();
        await this._refreshCategoryFilter();

        this._bindTabs();
        this._bindControls();
        this._bindActions();
        this.renderProductGrid();
        this.renderPreview();
    },

    // ══════════════════════════════════════════════════════════════
    // UTILITAIRES
    // ══════════════════════════════════════════════════════════════

    _populateWarehouseFilter(){
        const whSel = document.getElementById('lgWh');
        if(!whSel) return;
        const current = whSel.value || '';
        whSel.innerHTML = '<option value="">Tous magasins</option>' +
            (this.whs || []).map(w => `<option value="${this._esc(w.id)}">${this._esc(w.name || ('Magasin #' + w.id))}</option>`).join('');
        whSel.value = current;
    },

    async _refreshCategoryFilter(){
        const whSel = document.getElementById('lgWh');
        const catSel = document.getElementById('lgCat');
        if(!catSel) return;

        const selectedWh = whSel?.value || '';
        this.currentWarehouseId = selectedWh;

        let cats = [];
        if(selectedWh){
            try {
                cats = await Api.warehouseCategories(selectedWh);
            } catch(e) {
                console.warn('[BcPage] Impossible de charger les catégories du magasin', selectedWh, e);
                cats = [];
            }
            if(!Array.isArray(cats)) cats = [];
            if(!cats.length){
                cats = [...new Set((this.prods || [])
                    .filter(p => this._productInWarehouse(p, selectedWh))
                    .map(p => p.category)
                    .filter(Boolean))].sort((a,b)=>String(a).localeCompare(String(b),'fr'));
            }
        } else {
            cats = [...new Set((this.prods || []).map(p => p.category).filter(Boolean))].sort((a,b)=>String(a).localeCompare(String(b),'fr'));
        }

        const prev = catSel.value || '';
        catSel.innerHTML = '<option value="">Toutes catégories</option>' +
            cats.map(c => `<option value="${this._esc(c)}">${this._esc(c)}</option>`).join('');
        catSel.value = cats.includes(prev) ? prev : '';
    },

    _productInWarehouse(p, warehouseId){
        if(!warehouseId) return true;
        const target = String(warehouseId);
        const allowed = Array.isArray(p?.allowedWarehouses) ? p.allowedWarehouses.map(String) : [];
        if(allowed.length) return allowed.includes(target);
        if(p?.defaultWarehouse != null && p.defaultWarehouse !== '') return String(p.defaultWarehouse) === target;
        return true;
    },

    _esc(s){
        if(!s) return '';
        const d = document.createElement('div');
        d.textContent = s;
        return d.innerHTML;
    },

    _getImagePath(p){
        const fields = ['imagePath', 'image', 'imageUrl', 'photo', 'picture'];
        for(const f of fields){
            if(p[f]){
                const path = p[f];
                if(path.startsWith('http') || path.startsWith('data:')) return path;
                return path.startsWith('uploads/') ? path : `uploads/${path}`;
            }
        }
        return '';
    },

    _getCode(p){
        const custom = document.getElementById('lgCustomCode')?.value?.trim() || '';
        return custom || p.barcode || p.sku || p.name || '';
    },

    _getBarcodeFormat(){
        return document.getElementById('lgBarcodeFmt')?.value || 'CODE128';
    },

    _getOptions(){
        return {
            showName: document.getElementById('lgShowName')?.checked ?? true,
            showSku: document.getElementById('lgShowSku')?.checked ?? true,
            showCode: document.getElementById('lgShowCode')?.checked ?? true,
            showImage: document.getElementById('lgShowImage')?.checked ?? true
        };
    },

    _getCurrentFormat(){
        if(this.format === 'custom'){
            return {
                cols: parseInt(document.getElementById('lgCCols')?.value) || 3,
                rows: parseInt(document.getElementById('lgCRows')?.value) || 8,
                wmm: parseFloat(document.getElementById('lgCWidth')?.value) || 70,
                hmm: parseFloat(document.getElementById('lgCHeight')?.value) || 37,
                gapmm: 2.5,
                title: 'Format personnalisé'
            };
        }
        return this.formats[this.format] || this.formats.A4_24;
    },

    // ══════════════════════════════════════════════════════════════
    // BINDINGS
    // ══════════════════════════════════════════════════════════════
    _bindTabs(){
        document.querySelectorAll('.lg-tab').forEach(btn => {
            btn.onclick = () => {
                const tab = btn.dataset.tab;
                document.querySelectorAll('.lg-tab').forEach(x => x.classList.remove('active'));
                btn.classList.add('active');
                document.querySelectorAll('.lg-tabpane').forEach(p => p.classList.remove('active'));
                const pane = document.getElementById('lgTab' + tab.charAt(0).toUpperCase() + tab.slice(1));
                if(pane) pane.classList.add('active');
            };
        });
    },

    _bindControls(){
        // Style radio buttons
        document.querySelectorAll('input[name="lgStyle"]').forEach(r => {
            r.onchange = () => {
                this.style = document.querySelector('input[name="lgStyle"]:checked')?.value || 'img_barcode';
                this.renderPreview();
            };
        });

        // Barcode format & custom code
        document.getElementById('lgBarcodeFmt')?.addEventListener('change', () => this.renderPreview());
        document.getElementById('lgCustomCode')?.addEventListener('input', () => this.renderPreview());
        document.getElementById('lgCustomQr')?.addEventListener('input', () => this.renderPreview());

        // Format radio buttons
        document.querySelectorAll('input[name="lgFormat"]').forEach(r => {
            r.onchange = () => {
                this.format = document.querySelector('input[name="lgFormat"]:checked')?.value || 'A4_24';
                const customWrap = document.getElementById('lgCustomFormat');
                if(customWrap) customWrap.style.display = this.format === 'custom' ? 'block' : 'none';
                this.renderPreview();
            };
        });

        // Custom format fields
        ['lgCWidth','lgCHeight','lgCCols','lgCRows'].forEach(id => {
            document.getElementById(id)?.addEventListener('input', () => {
                if(this.format === 'custom') this.renderPreview();
            });
        });

        // Options checkboxes — toutes modifient la preview
        ['lgShowName','lgShowSku','lgShowCode','lgShowImage'].forEach(id => {
            document.getElementById(id)?.addEventListener('change', () => this.renderPreview());
        });

        // Search and filter
        document.getElementById('lgSearch')?.addEventListener('input', () => this.renderProductGrid());
        document.getElementById('lgWh')?.addEventListener('change', async () => {
            await this._refreshCategoryFilter();
            this.renderProductGrid();
        });
        document.getElementById('lgCat')?.addEventListener('change', () => this.renderProductGrid());
    },

    _bindActions(){
        document.getElementById('btnLgClear')?.addEventListener('click', () => this.clearSelection());
        document.getElementById('btnLgPdf')?.addEventListener('click', () => this.exportPDF());
        document.getElementById('btnLgPrint')?.addEventListener('click', () => this.doPrint());
        document.getElementById('btnLgQrScan')?.addEventListener('click', () => toast('Scanner non disponible ici', 'info'));

        // Print overlay buttons
        document.getElementById('lgBtnClosePrint')?.addEventListener('click', () => this.closePrintOverlay());
        document.getElementById('lgBtnDoPrint')?.addEventListener('click', () => this.launchPrint());
    },

    // ══════════════════════════════════════════════════════════════
    // SÉLECTION PRODUITS
    // ══════════════════════════════════════════════════════════════
    clearSelection(){
        this.selected.clear();
        this.renderProductGrid();
        this.renderPreview();
        toast('Sélection vidée', 'info');
    },

    toggleProduct(id){
        if(this.selected.has(id)){
            this.selected.delete(id);
        } else {
            this.selected.add(id);
        }
        this.renderProductGrid();
        this.renderPreview();
    },

    renderProductGrid(){
        const grid = document.getElementById('lgProdGrid');
        if(!grid) return;

        const search = (document.getElementById('lgSearch')?.value || '').toLowerCase();
        const wh = document.getElementById('lgWh')?.value || '';
        const cat = document.getElementById('lgCat')?.value || '';

        let filtered = this.prods.filter(p => {
            if(wh && !this._productInWarehouse(p, wh)) return false;
            if(cat && p.category !== cat) return false;
            if(search){
                const hay = `${p.name||''} ${p.sku||''} ${p.barcode||''}`.toLowerCase();
                if(!hay.includes(search)) return false;
            }
            return true;
        });

        grid.innerHTML = filtered.map(p => {
            const sel = this.selected.has(p.id);
            const img = this._getImagePath(p);
            return `
                <div class="lg-prod-card ${sel ? 'selected' : ''}"
                     onclick="BcPage.toggleProduct(${p.id})"
                     tabindex="0"
                     role="checkbox"
                     aria-checked="${sel}"
                     onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();BcPage.toggleProduct(${p.id})}">
                    <div class="lg-prod-check">${sel ? '✓' : ''}</div>
                    <div class="lg-prod-img">
                        ${img ? `<img src="${img}" onerror="this.parentElement.innerHTML='📦'" alt="">` : '📦'}
                    </div>
                    <div class="lg-prod-name">${this._esc(p.name)}</div>
                    <div class="lg-prod-sku">${this._esc(p.sku || p.barcode || '')}</div>
                </div>
            `;
        }).join('');
    },

    // ══════════════════════════════════════════════════════════════
    // PRÉVISUALISATION — Grille responsive structurée
    // ══════════════════════════════════════════════════════════════
    renderPreview(){
        const sample = document.getElementById('lgSample');
        const sub = document.getElementById('lgPreviewSub');
        const meta = document.getElementById('lgPreviewMeta');

        if(!sample) return;

        const count = this.selected.size;
        const fmt = this._getCurrentFormat();
        const perPage = fmt.cols * fmt.rows;

        sample.classList.toggle('has-items', count > 0);

        if(sub) sub.textContent = count > 0 ? `${count} produit(s)` : 'Aucune sélection';
        if(meta) meta.textContent = `${fmt.title} · ${perPage} étiquette(s)`;

        if(count === 0){
            sample.classList.remove('has-items');
            sample.innerHTML = '<div class="lg-empty">Sélectionnez des produits ci-dessous</div>';
            return;
        }

        // Preview : toutes les étiquettes sélectionnées (pas de limite)
        const previewProds = Array.from(this.selected)
            .map(id => this.prods.find(p => p.id === id))
            .filter(Boolean);

        // Colonnes de preview = min(4, fmt.cols) pour rester lisible
        const previewCols = Math.min(4, fmt.cols);

        sample.innerHTML = `
            <div class="lg-sample-grid" style="--lg-cols:${previewCols}">
                ${previewProds.map((p, i) => this._renderLabel(p, `prevBC${i}`, 'preview')).join('')}
            </div>
        `;

        // Générer les codes après insertion DOM
        setTimeout(() => {
            previewProds.forEach((p, i) => this._generateBarcode(p, `prevBC${i}`, true));
        }, 50);
    },

    // ══════════════════════════════════════════════════════════════
    // RENDU ÉTIQUETTE UNIFIÉ (preview + print + PDF même structure)
    // ══════════════════════════════════════════════════════════════
    /**
     * @param {Object} p - produit (ou null pour cellule vide)
     * @param {string} canvasId - id unique pour le canvas/QR
     * @param {string} context - 'preview' | 'print'
     * @returns {string} HTML
     */
    _renderLabel(p, canvasId, context){
        if(!p){
            return `<div class="lg-sheet-label lg-sheet-empty"></div>`;
        }

        const opts = this._getOptions();
        const custom = document.getElementById('lgCustomCode')?.value?.trim() || '';
        const showCustom = !!custom;
        const code = this._esc(custom);
        const img = this._getImagePath(p);
        const style = this.style;
        const cls = context === 'preview' ? 'lg-preview-label' : 'lg-sheet-label';

        // ─── Style "barcode" (code-barres seul) ───
        if(style === 'barcode'){
            let variantCls = context === 'preview' ? ' barcode-only' : '';
            return `
                <div class="${cls}${variantCls}">
                    <div class="lg-label-barcode-only">
                        ${opts.showName ? `<div class="lg-label-name">${this._esc(p.name || '')}</div>` : ''}
                        <div class="lg-bc-wrap"><canvas id="${canvasId}"></canvas></div>
                        ${opts.showSku ? `<div class="lg-label-sku">${this._esc(p.sku || p.barcode || '')}</div>` : ''}
                        ${(opts.showCode && showCustom) ? `<div class="lg-label-code">${code}</div>` : ''}
                    </div>
                </div>
            `;
        }

        // ─── Style "QR" ───
        if(style === 'qr'){
            const customQr = document.getElementById('lgCustomQr')?.value?.trim() || '';
            const payload = this._esc(customQr || JSON.stringify({type:'product', id:p.id, name:p.name, sku:p.sku || p.barcode}));
            let variantCls = context === 'preview' ? ' qr-style' : '';
            return `
                <div class="${cls}${variantCls}">
                    <div class="lg-label-qr">
                        <div class="lg-qr-wrap" data-qr="${payload}" id="${canvasId}"></div>
                        ${opts.showName ? `<div class="lg-label-name">${this._esc(p.name || '')}</div>` : ''}
                        ${opts.showSku ? `<div class="lg-label-sku">${this._esc(p.sku || p.barcode || '')}</div>` : ''}
                    </div>
                </div>
            `;
        }

        // ─── Style "img_barcode" (défaut) et "full" ───
        // Structure : [IMAGE gauche] + body(nom / barcode / sku / code)
        const imgHtml = opts.showImage && img
            ? (style === 'full'
                ? `<img src="${this._esc(img)}" class="lg-label-img-full" onerror="this.style.display='none'">`
                : `<img src="${this._esc(img)}" class="lg-label-img" onerror="this.style.display='none'">`)
            : '';

        const usesRow = style !== 'full';
        let variantCls = style === 'full' ? (context === 'preview' ? ' full-style' : '') : '';

        return `
            <div class="${cls}${variantCls}">
                ${style === 'full' ? `<div class="lg-label-full">` : ''}
                ${usesRow ? '<div class="lg-label-row">' : ''}
                    ${usesRow ? imgHtml : ''}
                    ${!usesRow ? imgHtml : ''}
                    <div class="lg-label-body">
                        ${opts.showName ? `<div class="lg-label-name">${this._esc(p.name || '')}</div>` : ''}
                        <div class="lg-bc-wrap"><canvas id="${canvasId}"></canvas></div>
                        ${opts.showSku ? `<div class="lg-label-sku">${this._esc(p.sku || p.barcode || '')}</div>` : ''}
                        ${(opts.showCode && showCustom) ? `<div class="lg-label-code">${code}</div>` : ''}
                    </div>
                ${usesRow ? '</div>' : ''}
                ${style === 'full' ? '</div>' : ''}
            </div>
        `;
    },

    _generateBarcode(p, elementId, isPreview = false){
        const code = this._getCode(p);
        const fmt = this._getBarcodeFormat();

        if(this.style === 'qr'){
            const div = document.getElementById(elementId);
            if(div && typeof qrcode !== 'undefined'){
                const customQr = document.getElementById('lgCustomQr')?.value?.trim() || '';
                const txt = customQr || JSON.stringify({type:'product', id:p.id, name:p.name, sku:p.sku||p.barcode});
                try {
                    const qr = qrcode(0, 'M');
                    qr.addData(txt);
                    qr.make();
                    div.innerHTML = qr.createImgTag(isPreview ? 3 : 4);
                } catch(e) {
                    console.error('QR error:', e);
                    div.innerHTML = '<span style="color:red;font-size:10px">Erreur QR</span>';
                }
            }
        } else {
            const canvas = document.getElementById(elementId);
            if(canvas && typeof JsBarcode !== 'undefined' && code){
                try {
                    JsBarcode(canvas, code, {
                        format: fmt,
                        width: isPreview ? 2 : 1.5,
                        height: isPreview ? 40 : 30,
                        displayValue: false,
                        margin: 0
                    });
                } catch(e) {
                    console.error('Barcode error:', e);
                    const parent = canvas.parentElement;
                    if(parent){
                        parent.innerHTML = `<div style="font-size:8pt;text-align:center;padding:5px;color:red">Code invalide</div>`;
                    }
                }
            }
        }
    },

    // ══════════════════════════════════════════════════════════════
    // IMPRESSION — Fenêtre popup
    // ══════════════════════════════════════════════════════════════
    doPrint(){
        if(this.selected.size === 0){
            toast('Sélectionnez au moins un produit', 'warning');
            return;
        }
        this._openPrintWindow('print');
    },


    exportPDF(){
        if(this.selected.size === 0){
            toast('Sélectionnez au moins un produit', 'warning');
            return;
        }
        this._exportPdfViaCanvas().catch(err => {
            console.error('PDF export error:', err);
            toast('Export PDF échoué : ' + err.message, 'error');
        });
    },

    // ══════════════════════════════════════════════════════════════
    // PDF EXPORT — canvas → JPEG → PDF minimal (100% offline)
    // ══════════════════════════════════════════════════════════════
    async _exportPdfViaCanvas(){
        const fmt          = this._getCurrentFormat();
        const opts         = this._getOptions();
        const barcodeFormat= this._getBarcodeFormat();
        const customCode   = document.getElementById('lgCustomCode')?.value?.trim() || '';
        const customQr     = document.getElementById('lgCustomQr')?.value?.trim()   || '';

        const labels = Array.from(this.selected)
            .map(id => this.prods.find(p => p.id === id))
            .filter(Boolean);

        const perPage = fmt.cols * fmt.rows;
        const pages   = [];
        for(let i = 0; i < labels.length; i += perPage){
            const pg = labels.slice(i, i + perPage);
            while(pg.length < perPage) pg.push(null);
            pages.push(pg);
        }

        toast('Génération PDF…', 'info');

        const DPI      = 300;
        const PX_MM    = DPI / 25.4;
        const PAGE_W   = Math.round(210 * PX_MM);   // A4 largeur
        const PAGE_H   = Math.round(297 * PX_MM);   // A4 hauteur
        const MARGIN   = Math.round(8   * PX_MM);
        const GAP      = Math.round(fmt.gapmm * PX_MM);
        const LBL_W    = Math.round(fmt.wmm   * PX_MM);
        const LBL_H    = Math.round(fmt.hmm   * PX_MM);
        const PAD      = Math.round(2   * PX_MM);   // padding interne étiquette
        const CUT      = Math.round(3   * PX_MM);   // longueur repère découpe
        const CUT_PX   = 0.7;                        // épaisseur repère (px à 300dpi)

        const loadImg = src => new Promise(res => {
            if(!src){ res(null); return; }
            const img     = new Image();
            img.crossOrigin = 'anonymous';
            img.onload  = () => res(img);
            img.onerror = () => res(null);
            img.src = src.startsWith('http') || src.startsWith('data:') ? src
                    : (window.location.href.replace(/\/[^/]*$/, '/') + src);
        });

        const setFont = (ctx, sizePt, bold, mono) => {
            const fam = mono ? "'Courier New',monospace" : "Arial,sans-serif";
            ctx.font = `${bold?'700':'400'} ${Math.round(sizePt * DPI / 72)}px ${fam}`;
        };

        const clampText = (ctx, text, maxW) => {
            let t = String(text||'');
            if(ctx.measureText(t).width <= maxW) return t;
            while(t.length > 1 && ctx.measureText(t+'…').width > maxW) t = t.slice(0,-1);
            return t + '…';
        };

        const canvasPages = [];

        for(const pg of pages){
            const cv  = document.createElement('canvas');
            cv.width  = PAGE_W;
            cv.height = PAGE_H;
            const ctx = cv.getContext('2d', {alpha: false});
            ctx.fillStyle = '#ffffff';
            ctx.fillRect(0, 0, PAGE_W, PAGE_H);

            // Largeur totale de la grille d'étiquettes
            const GRID_W = fmt.cols * LBL_W + (fmt.cols - 1) * GAP;
            const START_X = Math.round((PAGE_W - GRID_W) / 2);
            const START_Y = MARGIN;

            for(let i = 0; i < pg.length; i++){
                const p  = pg[i];
                const col = i % fmt.cols;
                const row = Math.floor(i / fmt.cols);
                const x  = START_X + col * (LBL_W + GAP);
                const y  = START_Y + row * (LBL_H + GAP);

                // ── Repères de découpe aux 4 coins (lignes noires, fiables) ──
                ctx.strokeStyle = '#999';
                ctx.lineWidth   = CUT_PX;
                // Coin haut-gauche
                ctx.beginPath(); ctx.moveTo(x - CUT, y);   ctx.lineTo(x,   y);   ctx.stroke();
                ctx.beginPath(); ctx.moveTo(x,   y - CUT); ctx.lineTo(x,   y);   ctx.stroke();
                // Coin haut-droite
                ctx.beginPath(); ctx.moveTo(x + LBL_W,     y); ctx.lineTo(x + LBL_W + CUT, y); ctx.stroke();
                ctx.beginPath(); ctx.moveTo(x + LBL_W, y - CUT); ctx.lineTo(x + LBL_W,  y); ctx.stroke();
                // Coin bas-gauche
                ctx.beginPath(); ctx.moveTo(x - CUT, y + LBL_H); ctx.lineTo(x,   y + LBL_H); ctx.stroke();
                ctx.beginPath(); ctx.moveTo(x,   y + LBL_H); ctx.lineTo(x,   y + LBL_H + CUT); ctx.stroke();
                // Coin bas-droite
                ctx.beginPath(); ctx.moveTo(x + LBL_W,     y + LBL_H); ctx.lineTo(x + LBL_W + CUT, y + LBL_H); ctx.stroke();
                ctx.beginPath(); ctx.moveTo(x + LBL_W, y + LBL_H);     ctx.lineTo(x + LBL_W, y + LBL_H + CUT); ctx.stroke();

                if(!p) continue;

                const code    = customCode || p.barcode || p.sku || p.name || '';
                const imgSrc  = opts.showImage ? this._getImagePath(p) : '';
                const imgEl   = imgSrc ? await loadImg(imgSrc) : null;

                const IX = x + PAD;
                const IY = y + PAD;
                const IW = LBL_W - PAD * 2;
                const IH = LBL_H - PAD * 2;

                if(this.style === 'barcode'){
                    // ── Code-barres seul : centré verticalement ──
                    let ty = IY;
                    const bcH = Math.min(IH * 0.45, Math.round(10 * PX_MM));
                    const nameH  = opts.showName ? Math.round(2.8 * PX_MM) : 0;
                    const skuH   = opts.showSku  ? Math.round(2.2 * PX_MM) : 0;
                    const totalH = nameH + bcH + skuH;
                    ty = IY + Math.round((IH - totalH) / 2);

                    if(opts.showName){
                        setFont(ctx, 7, true, false);
                        ctx.fillStyle = '#111';
                        ctx.textAlign = 'center';
                        ctx.fillText(clampText(ctx, p.name||'', IW), IX + IW/2, ty + nameH);
                        ty += nameH + Math.round(1*PX_MM);
                    }
                    const bcCv = document.createElement('canvas');
                    try{
                        JsBarcode(bcCv, code, {format:barcodeFormat, width:2, height:60, displayValue:false, margin:0});
                        ctx.drawImage(bcCv, IX, ty, IW, bcH);
                    }catch(e){}
                    ty += bcH + Math.round(1*PX_MM);
                    if(opts.showSku){
                        setFont(ctx, 6, false, true);
                        ctx.fillStyle = '#444';
                        ctx.textAlign = 'center';
                        ctx.fillText(clampText(ctx, p.sku||p.barcode||'', IW), IX + IW/2, ty + skuH);
                    }
                    ctx.textAlign = 'left';
                }
                else if(this.style === 'qr'){
                    // ── QR : centré verticalement ──
                    const qrSize = Math.min(IH * 0.55, Math.round(16 * PX_MM));
                    const nameH  = opts.showName ? Math.round(2.8 * PX_MM) : 0;
                    const skuH   = opts.showSku  ? Math.round(2.2 * PX_MM) : 0;
                    const totalH = qrSize + nameH + skuH;
                    let   ty     = IY + Math.round((IH - totalH) / 2);

                    // Rendu QR via lib
                    if(typeof qrcode !== 'undefined'){
                        try{
                            const qrTxt = customQr||JSON.stringify({type:'product',id:p.id,name:p.name,sku:p.sku||p.barcode});
                            const qr    = qrcode(0,'M');
                            qr.addData(qrTxt); qr.make();
                            const qrImg = new Image();
                            const qrUrl = 'data:image/svg+xml;base64,' + btoa(qr.createSvgTag());
                            await new Promise(res => { qrImg.onload = qrImg.onerror = res; qrImg.src = qrUrl; });
                            if(qrImg.complete && qrImg.naturalWidth)
                                ctx.drawImage(qrImg, IX + Math.round((IW-qrSize)/2), ty, qrSize, qrSize);
                        }catch(e){}
                    }
                    ty += qrSize + Math.round(1*PX_MM);
                    ctx.textAlign = 'center';
                    if(opts.showName){
                        setFont(ctx, 7, true, false);
                        ctx.fillStyle = '#111';
                        ctx.fillText(clampText(ctx, p.name||'', IW), IX + IW/2, ty + nameH);
                        ty += nameH + Math.round(1*PX_MM);
                    }
                    if(opts.showSku){
                        setFont(ctx, 6, false, true);
                        ctx.fillStyle = '#444';
                        ctx.fillText(clampText(ctx, p.sku||p.barcode||'', IW), IX + IW/2, ty + skuH);
                    }
                    ctx.textAlign = 'left';
                }
                else {
                    // ── img_barcode / full : image gauche + body droite, centré ──
                    const IMG_W   = (imgEl && opts.showImage) ? Math.min(Math.round(14*PX_MM), Math.round(IW*0.28)) : 0;
                    const IMG_H   = IMG_W;
                    const GAP_IMG = IMG_W ? Math.round(2*PX_MM) : 0;
                    const BODY_X  = IX + IMG_W + GAP_IMG;
                    const BODY_W  = IW - IMG_W - GAP_IMG;

                    const nameH = opts.showName ? Math.round(2.8*PX_MM) : 0;
                    const bcH   = Math.min(Math.round(IH * 0.38), Math.round(10*PX_MM));
                    const skuH  = opts.showSku  ? Math.round(2.2*PX_MM) : 0;
                    const totalH = nameH + Math.round(1*PX_MM) + bcH + Math.round(1*PX_MM) + skuH;
                    let ty = IY + Math.round((IH - totalH) / 2);

                    // Image centrée verticalement
                    if(imgEl && IMG_W){
                        const imgOffY = IY + Math.round((IH - IMG_H) / 2);
                        ctx.drawImage(imgEl, IX, imgOffY, IMG_W, IMG_H);
                    }

                    // Nom
                    if(opts.showName){
                        setFont(ctx, 7, true, false);
                        ctx.fillStyle = '#111';
                        ctx.textAlign = 'left';
                        ctx.fillText(clampText(ctx, p.name||'', BODY_W), BODY_X, ty + nameH);
                        ty += nameH + Math.round(1*PX_MM);
                    }

                    // Code-barres
                    const bcCv = document.createElement('canvas');
                    try{
                        JsBarcode(bcCv, code, {format:barcodeFormat, width:2, height:60, displayValue:false, margin:0});
                        ctx.drawImage(bcCv, BODY_X, ty, BODY_W, bcH);
                    }catch(e){}
                    ty += bcH + Math.round(1*PX_MM);

                    // SKU
                    if(opts.showSku){
                        setFont(ctx, 6, false, true);
                        ctx.fillStyle = '#444';
                        ctx.textAlign = 'center';
                        ctx.fillText(clampText(ctx, p.sku||p.barcode||'', BODY_W), BODY_X + BODY_W/2, ty + skuH);
                    }
                    ctx.textAlign = 'left';
                }
            }
            canvasPages.push(cv);
        }

        // ── Assembler PDF minimal (JPEG pages) ──
        const pdfBytes = this._buildPdfFromCanvases(canvasPages);
        const blob     = new Blob([pdfBytes], {type:'application/pdf'});
        const stamp    = new Date().toISOString().slice(0,10);
        const url      = URL.createObjectURL(blob);
        const a        = document.createElement('a');
        a.href = url; a.download = `codes-barres_${stamp}.pdf`;
        document.body.appendChild(a); a.click(); a.remove();
        setTimeout(() => URL.revokeObjectURL(url), 2000);
        toast('PDF téléchargé ✓', 'success');
    },

    _buildPdfFromCanvases(canvases){
        const enc  = s  => new TextEncoder().encode(s);
        const cat  = chunks => {
            const len = chunks.reduce((a,c)=>a+c.length,0);
            const out = new Uint8Array(len);
            let o = 0;
            for(const c of chunks){ out.set(c,o); o+=c.length; }
            return out;
        };

        const PT_W = 595.28, PT_H = 841.89;   // A4 en points
        let   oid  = 0;
        const objs = [];
        const offs = [];
        const add  = data => { oid++; objs.push({id:oid, data}); return oid; };

        const pagesId  = add(enc('<< /Type /Pages /Kids [] /Count 0 >>'));
        const catalogId= add(enc(`<< /Type /Catalog /Pages ${pagesId} 0 R >>`));
        const kids     = [];

        for(let i=0; i<canvases.length; i++){
            const cv  = canvases[i];
            const b64 = cv.toDataURL('image/jpeg', 0.92).split(',')[1]||'';
            const bin = atob(b64);
            const jpg = new Uint8Array(bin.length);
            for(let j=0;j<bin.length;j++) jpg[j]=bin.charCodeAt(j);

            const imgId = add(cat([
                enc(`<< /Type /XObject /Subtype /Image /Width ${cv.width} /Height ${cv.height} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length ${jpg.length} >>\nstream\n`),
                jpg,
                enc('\nendstream')
            ]));
            const cs = `q\n${PT_W} 0 0 ${PT_H} 0 0 cm\n/Im${i} Do\nQ\n`;
            const csId = add(enc(`<< /Length ${cs.length} >>\nstream\n${cs}endstream`));
            const pgId = add(enc(
                `<< /Type /Page /Parent ${pagesId} 0 R /MediaBox [0 0 ${PT_W} ${PT_H}] ` +
                `/Resources << /XObject << /Im${i} ${imgId} 0 R >> >> /Contents ${csId} 0 R >>`
            ));
            kids.push(pgId);
        }

        objs[0].data = enc(`<< /Type /Pages /Kids [${kids.map(k=>k+' 0 R').join(' ')}] /Count ${kids.length} >>`);

        const hdr = enc('%PDF-1.4\n%\xE2\xE3\xCF\xD3\n');
        const parts = [hdr];
        let offset = hdr.length;
        offs.push(0);
        for(const o of objs){
            offs.push(offset);
            const h = enc(`${o.id} 0 obj\n`);
            const t = enc('\nendobj\n');
            parts.push(h, o.data, t);
            offset += h.length + o.data.length + t.length;
        }
        const xref = ['xref\n', `0 ${objs.length+1}\n`, '0000000000 65535 f \n',
            ...offs.slice(1).map(o => String(o).padStart(10,'0')+' 00000 n \n')].join('');
        parts.push(enc(xref + `trailer\n<< /Size ${objs.length+1} /Root ${catalogId} 0 R >>\nstartxref\n${offset}\n%%EOF`));
        return cat(parts);
    },

    // ══════════════════════════════════════════════════════════════
    // FENÊTRE IMPRESSION — CSS inliné + repères SVG (fiables print)
    // ══════════════════════════════════════════════════════════════
    _openPrintWindow(mode){
        const fmt           = this._getCurrentFormat();
        const opts          = this._getOptions();
        const barcodeFormat = this._getBarcodeFormat();
        const customCode    = document.getElementById('lgCustomCode')?.value?.trim() || '';
        const customQr      = document.getElementById('lgCustomQr')?.value?.trim()   || '';

        const labels = Array.from(this.selected)
            .map(id => this.prods.find(p => p.id === id))
            .filter(Boolean);

        const perPage = fmt.cols * fmt.rows;
        const pages   = [];
        for(let i = 0; i < labels.length; i += perPage){
            const pg = labels.slice(i, i + perPage);
            while(pg.length < perPage) pg.push(null);
            pages.push(pg);
        }

        const printWin = window.open('', '_blank', 'width=1000,height=750');
        if(!printWin){
            toast('Popup bloquée — autorisez les popups pour ce site.', 'error');
            return;
        }
        const html = this._generatePrintHTML(pages, fmt, opts, barcodeFormat, customCode, customQr, mode);
        printWin.document.write(html);
        printWin.document.close();
    },

    _generatePrintHTML(pages, fmt, opts, barcodeFormat, customCode, customQr, mode){
        const style = this.style;
        const wmm   = fmt.wmm;
        const hmm   = fmt.hmm;
        const gapmm = fmt.gapmm;
        const cols  = fmt.cols;

        const baseHref = (() => {
            try{
                const u     = new URL(window.location.href);
                const parts = u.pathname.split('/').filter(Boolean);
                parts.pop();
                return `${u.origin}/${parts.join('/')}/`;
            } catch(e){ return '/'; }
        })();

        const esc = s => (s||'').replace(/[&<>"']/g, m =>
            ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[m]));

        const pagesData = JSON.stringify(pages.map(pg => pg.map(p => p ? {
            id:p.id, name:p.name||'', sku:p.sku||'',
            barcode:p.barcode||'', image:this._getImagePath(p)||''
        } : null)));

        // Repères de découpe : 4 <span> positionnés avec border CSS
        // Technique fiable à l'impression (pas de background linear-gradient)
        const cutMarks = `
<span class="cut cut-tl-h"></span>
<span class="cut cut-tl-v"></span>
<span class="cut cut-tr-h"></span>
<span class="cut cut-tr-v"></span>
<span class="cut cut-bl-h"></span>
<span class="cut cut-bl-v"></span>
<span class="cut cut-br-h"></span>
<span class="cut cut-br-v"></span>`;

        const renderLabel = (p, cid) => {
            if(!p) return `<div class="lbl lbl-empty">${cutMarks}</div>`;
            const code      = esc(customCode||'');
            const showCustom= !!(customCode && customCode.trim());
            const imgHtml   = opts.showImage && p.image
                ? `<img class="lbl-img" src="${esc(p.image)}" onerror="this.style.display='none'" alt="">`
                : '';

            if(style === 'barcode') return `<div class="lbl">${cutMarks}
<div class="lbl-center">
  ${opts.showName  ? `<div class="lbl-name">${esc(p.name)}</div>` : ''}
  <div class="lbl-bwrap"><canvas id="${cid}"></canvas></div>
  ${opts.showSku   ? `<div class="lbl-sku">${esc(p.sku||p.barcode||'')}</div>` : ''}
  ${(opts.showCode&&showCustom) ? `<div class="lbl-code">${code}</div>` : ''}
</div></div>`;

            if(style === 'qr'){
                const qrp = esc(customQr||JSON.stringify({type:'product',id:p.id,name:p.name,sku:p.sku||p.barcode}));
                return `<div class="lbl">${cutMarks}
<div class="lbl-center">
  <div class="lbl-qrwrap" id="${cid}"></div>
  ${opts.showName ? `<div class="lbl-name">${esc(p.name)}</div>` : ''}
  ${opts.showSku  ? `<div class="lbl-sku">${esc(p.sku||p.barcode||'')}</div>` : ''}
</div></div>`;
            }

            if(style === 'full') return `<div class="lbl">${cutMarks}
<div class="lbl-full">
  ${imgHtml}
  <div class="lbl-body">
    ${opts.showName ? `<div class="lbl-name">${esc(p.name)}</div>` : ''}
    <div class="lbl-bwrap"><canvas id="${cid}"></canvas></div>
    ${opts.showSku ? `<div class="lbl-sku">${esc(p.sku||p.barcode||'')}</div>` : ''}
    ${(opts.showCode&&showCustom) ? `<div class="lbl-code">${code}</div>` : ''}
  </div>
</div></div>`;

            // img_barcode (défaut)
            return `<div class="lbl">${cutMarks}
<div class="lbl-row">
  ${imgHtml}
  <div class="lbl-body">
    ${opts.showName ? `<div class="lbl-name">${esc(p.name)}</div>` : ''}
    <div class="lbl-bwrap"><canvas id="${cid}"></canvas></div>
    ${opts.showSku ? `<div class="lbl-sku">${esc(p.sku||p.barcode||'')}</div>` : ''}
    ${(opts.showCode&&showCustom) ? `<div class="lbl-code">${code}</div>` : ''}
  </div>
</div></div>`;
        };

        let body = '';
        pages.forEach((pg, pi) => {
            body += `<div class="sheet" style="--cols:${cols};--wmm:${wmm}mm;--hmm:${hmm}mm;--gapmm:${gapmm}mm">`;
            pg.forEach((p, li) => { body += renderLabel(p, `bc_${pi}_${li}`); });
            body += '</div>';
        });

        const CSS = `
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html,body{background:#e8eaed;font-family:Arial,Helvetica,sans-serif}

/* ── FEUILLE ── */
.sheet{
  display:grid;
  grid-template-columns:repeat(var(--cols),var(--wmm));
  grid-auto-rows:var(--hmm);
  gap:var(--gapmm);
  width:calc(var(--cols)*var(--wmm) + (var(--cols) - 1)*var(--gapmm));
  background:#fff;
  margin:20px auto;
  padding:8mm;
  box-shadow:0 4px 20px rgba(0,0,0,.18);
  border-radius:4px;
}

/* ── ÉTIQUETTE ── */
.lbl{
  position:relative;
  width:var(--wmm);
  height:var(--hmm);
  padding:2mm;
  overflow:visible;
  background:#fff;
  border:0.4px solid #d0d0d0;
}
.lbl-empty{border-style:dashed;border-color:#bbb}

/* ── REPÈRES DE DÉCOUPE — spans HTML (fiables à l'impression) ── */
.cut{position:absolute;background:#888;pointer-events:none}
/* Coin haut-gauche */
.cut-tl-h{top:0;left:-3mm;width:3mm;height:0.3pt}
.cut-tl-v{top:-3mm;left:0;width:0.3pt;height:3mm}
/* Coin haut-droite */
.cut-tr-h{top:0;right:-3mm;width:3mm;height:0.3pt}
.cut-tr-v{top:-3mm;right:0;width:0.3pt;height:3mm}
/* Coin bas-gauche */
.cut-bl-h{bottom:0;left:-3mm;width:3mm;height:0.3pt}
.cut-bl-v{bottom:-3mm;left:0;width:0.3pt;height:3mm}
/* Coin bas-droite */
.cut-br-h{bottom:0;right:-3mm;width:3mm;height:0.3pt}
.cut-br-v{bottom:-3mm;right:0;width:0.3pt;height:3mm}

/* ── LAYOUTS ÉTIQUETTE ── */
/* Centrage vertical universel */
.lbl-center{
  display:flex;flex-direction:column;align-items:center;
  justify-content:center;height:100%;gap:0.8mm;
  text-align:center;
}
/* Ligne image + body */
.lbl-row{
  display:flex;align-items:center;gap:2mm;height:100%;
}
/* Style full */
.lbl-full{
  display:flex;flex-direction:column;align-items:center;
  justify-content:center;height:100%;gap:0.8mm;
}

/* ── ÉLÉMENTS ── */
.lbl-img{width:14mm;height:14mm;flex:0 0 14mm;object-fit:contain;border:0.3pt solid #ddd}
.lbl-body{
  flex:1;min-width:0;
  display:flex;flex-direction:column;
  justify-content:center;
  gap:0.5mm;
}
.lbl-name{font-size:7pt;font-weight:700;line-height:1.2;color:#111;
  overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}
.lbl-bwrap{width:100%;text-align:center}
.lbl-bwrap canvas{width:100%!important;height:auto!important;max-height:9mm!important;display:block!important}
.lbl-sku{font-family:'Courier New',monospace;font-size:6pt;color:#444;text-align:center}
.lbl-code{font-family:'Courier New',monospace;font-size:6pt;color:#666;text-align:center}
.lbl-qrwrap img{width:14mm!important;height:14mm!important;display:block;margin:0 auto}

/* ── IMPRESSION ── */
@media print{
  @page{size:A4 portrait;margin:0}
  html,body{background:#fff!important;margin:0!important;padding:0!important}
  .sheet{
    display:grid!important;
    grid-template-columns:repeat(var(--cols),var(--wmm))!important;
    grid-auto-rows:var(--hmm)!important;
    gap:var(--gapmm)!important;
    width:calc(var(--cols)*var(--wmm) + (var(--cols) - 1)*var(--gapmm))!important;
    margin:8mm auto 0!important;
    padding:0!important;
    box-shadow:none!important;
    page-break-after:always!important;
    background:#fff!important;
  }
  .sheet:last-child{page-break-after:auto!important}
  .lbl{
    overflow:visible!important;
    border:none!important;
    break-inside:avoid!important;
    page-break-inside:avoid!important;
  }
  /* Repères noirs garantis à l'impression */
  .cut{background:#888!important;-webkit-print-color-adjust:exact!important;print-color-adjust:exact!important}
}`;

        const JS = `(function(){
  var STYLE=${JSON.stringify(style)};
  var FMT=${JSON.stringify(barcodeFormat)};
  var CC=${JSON.stringify(customCode||'')};
  var CQ=${JSON.stringify(customQr||'')};
  var PGS=${pagesData};
  function code(p){return CC||p.barcode||p.sku||p.name||'';}
  function bar(p,id){
    var c=document.getElementById(id);if(!c)return;
    var v=code(p);if(!v)return;
    try{JsBarcode(c,v,{format:FMT,width:2,height:40,displayValue:false,margin:0});}
    catch(e){var w=c.parentElement;if(w)w.innerHTML='<div style="font-size:7pt;text-align:center;padding:1mm">'+v+'</div>';}
  }
  function qr(p,id){
    var el=document.getElementById(id);if(!el)return;
    try{
      var txt=CQ||JSON.stringify({type:'product',id:p.id,name:p.name,sku:p.sku||p.barcode});
      var q=qrcode(0,'M');q.addData(txt);q.make();
      el.innerHTML=q.createImgTag(3);
    }catch(e){el.innerHTML='<span style="color:red;font-size:7pt">Erreur QR</span>';}
  }
  PGS.forEach(function(pg,pi){pg.forEach(function(p,li){
    if(!p)return;
    var id='bc_'+pi+'_'+li;
    if(STYLE==='qr')qr(p,id);else bar(p,id);
  });});
  function waitImgs(){
    var imgs=[].slice.call(document.images);
    if(!imgs.length)return Promise.resolve();
    return Promise.all(imgs.map(function(img){
      if(img.complete)return Promise.resolve();
      return new Promise(function(r){img.onload=r;img.onerror=r;});
    }));
  }
  async function run(){
    await waitImgs();
    await new Promise(function(r){requestAnimationFrame(function(){requestAnimationFrame(r);});});
    window.focus();window.print();
    ${mode==='print'?'setTimeout(function(){window.close();},600);':''}
  }
  run();
})();`;

        return `<!doctype html>
<html lang="fr">
<head>
  <meta charset="utf-8">
  <base href="${baseHref}">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>${mode==='pdf'?'Export PDF':'Impression'} — StockMaster Pro</title>
  <style>${CSS}<\/style>
  <script src="libs/jsbarcode.all.min.js"><\/script>
  <script src="libs/qrcode.js"><\/script>
</head>
<body>
${body}
<script>${JS}<\/script>
</body>
</html>`;
    },


    // Rétro-compatibilité
    closePrintOverlay(){
        const overlay = document.getElementById('lgPrintOverlay');
        if(overlay) overlay.style.display = 'none';
    },
    launchPrint(){
        this.closePrintOverlay();
        this.doPrint();
    },
    generatePrintSheet(){
        this.doPrint();
    }
};
