/**
 * Fichier  : assets/js/pages/barcodes-mobile.js
 * Auteur   : KREMER Régis
 * Version  : 2.6.87
 * Date     : 2026-07-20
 * Description : Assistant mobile/tablette de génération des étiquettes MageStock.
 *
 * Changelog :
 *   v2.6.83 — BARCODES WIZARD7 : sélection filtrée, style, format/options,
 *              aperçu tactile, vérification et sorties impression/PDF.
 *   v2.6.87 — RECETTE11 : repli image produit sans erreur JavaScript lorsque la source distante échoue.
 */
(function barcodesMobileWizard(global, document) {
    'use strict';

    if (!global || !document || global.MageStockBarcodesMobile) return;
    if (typeof BcPage === 'undefined') return;

    const Page = BcPage;
    const VERSION = '2.6.87';
    const FLOW_KEY = 'magestock.barcodes.wizard.form.v1';
    const STATE_KEY = 'magestock.barcodes.wizard.state.v1';
    const BATCH_SIZE = 36;
    const PREVIEW_LIMIT = 6;
    const state = {
        initialized: false,
        flow: null,
        search: '',
        warehouse: '',
        category: '',
        renderCount: BATCH_SIZE,
        reviewPage: 1,
        filterHandle: null,
        renderTimer: null
    };

    global.BcPage = Page;

    const UI = () => global.MageStockMobileUI || null;
    const isAdaptive = () => {
        const profile = UI()?.device?.profile;
        return profile ? profile !== 'desktop' : (global.innerWidth || 0) < 1200;
    };
    const root = () => document.getElementById('bcMobileWizard');
    const byId = (id) => document.getElementById(id);
    const esc = (value) => String(value == null ? '' : value)
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#039;');
    const number = (value, fallback = 0) => {
        const parsed = Number.parseFloat(value);
        return Number.isFinite(parsed) ? parsed : fallback;
    };
    const int = (value, fallback = 0) => {
        const parsed = Number.parseInt(value, 10);
        return Number.isFinite(parsed) ? parsed : fallback;
    };
    const announce = (message, priority) => {
        if (UI()?.announce) UI().announce(message, priority || 'polite');
    };
    const notify = (message, type) => {
        if (typeof global.toast === 'function') global.toast(message, type || 'info');
        else announce(message, type === 'warning' || type === 'error' ? 'assertive' : 'polite');
    };

    function selectedTotals() {
        let labels = 0;
        Page.selected.forEach((qty) => { labels += Math.max(0, int(qty, 0)); });
        return { products: Page.selected.size, labels };
    }

    function selectedEntries() {
        return [...Page.selected.entries()]
            .map(([id, qty]) => [String(id), Math.max(1, int(qty, 1))]);
    }

    function saveState() {
        try {
            sessionStorage.setItem(STATE_KEY, JSON.stringify({
                version: 1,
                selected: selectedEntries(),
                warehouse: state.warehouse,
                category: state.category,
                renderCount: state.renderCount,
                reviewPage: state.reviewPage,
                savedAt: new Date().toISOString()
            }));
        } catch (_) {}
    }

    function restoreState() {
        try {
            const raw = sessionStorage.getItem(STATE_KEY);
            if (!raw) return false;
            const draft = JSON.parse(raw);
            const validIds = new Set((Page.prods || []).map((product) => String(product.id)));
            Page.selected.clear();
            (Array.isArray(draft.selected) ? draft.selected : []).forEach((entry) => {
                if (!Array.isArray(entry) || entry.length < 2) return;
                const id = String(entry[0]);
                const qty = Math.max(1, Math.min(100, int(entry[1], 1)));
                if (!validIds.has(id)) return;
                const product = (Page.prods || []).find((candidate) => String(candidate.id) === id);
                if (product) Page.selected.set(product.id, qty);
            });
            state.warehouse = String(draft.warehouse || '');
            state.category = String(draft.category || '');
            state.renderCount = Math.max(BATCH_SIZE, int(draft.renderCount, BATCH_SIZE));
            state.reviewPage = Math.max(1, int(draft.reviewPage, 1));
            return true;
        } catch (_) {
            return false;
        }
    }

    function clearSavedState() {
        try {
            sessionStorage.removeItem(STATE_KEY);
            sessionStorage.removeItem(FLOW_KEY);
        } catch (_) {}
    }

    function getSelectedRadio(name, fallback) {
        return document.querySelector(`input[name="${name}"]:checked`)?.value || fallback;
    }

    function setValue(id, value) {
        const control = byId(id);
        if (control) control.value = value == null ? '' : String(value);
    }

    function setChecked(id, checked) {
        const control = byId(id);
        if (control) control.checked = !!checked;
    }

    function syncToDesktop() {
        const style = getSelectedRadio('bcMobStyle', 'img_barcode');
        const format = getSelectedRadio('bcMobFormat', 'A4_21');
        Page.style = style;
        Page.format = format;

        document.querySelectorAll('input[name="lgStyle"]').forEach((radio) => {
            radio.checked = radio.value === style;
        });
        document.querySelectorAll('input[name="lgFormat"]').forEach((radio) => {
            radio.checked = radio.value === format;
        });

        setValue('lgBarcodeFmt', byId('bcMobBarcodeFmt')?.value || 'CODE128');
        setValue('lgCustomCode', byId('bcMobCustomCode')?.value || '');
        setValue('lgCustomQr', byId('bcMobCustomQr')?.value || '');
        setValue('lgCWidth', byId('bcMobCWidth')?.value || '70');
        setValue('lgCHeight', byId('bcMobCHeight')?.value || '37');
        setValue('lgCCols', byId('bcMobCCols')?.value || '3');
        setValue('lgCRows', byId('bcMobCRows')?.value || '7');
        setChecked('lgShowName', byId('bcMobShowName')?.checked ?? true);
        setChecked('lgShowSku', byId('bcMobShowSku')?.checked ?? true);
        setChecked('lgShowCode', byId('bcMobShowCode')?.checked ?? true);
        setChecked('lgShowImage', byId('bcMobShowImage')?.checked ?? true);
        setChecked('lgShowSize', byId('bcMobShowSize')?.checked ?? false);

        const customDesktop = byId('lgCustomFormat');
        if (customDesktop) customDesktop.style.display = format === 'custom' ? 'block' : 'none';
    }

    function syncFromDesktopDefaults() {
        const savedFlow = (() => {
            try { return sessionStorage.getItem(FLOW_KEY); } catch (_) { return null; }
        })();
        if (savedFlow) return;
        const style = Page.style || getSelectedRadio('lgStyle', 'img_barcode');
        const format = Page.format || getSelectedRadio('lgFormat', 'A4_21');
        const styleRadio = document.querySelector(`input[name="bcMobStyle"][value="${style}"]`);
        const formatRadio = document.querySelector(`input[name="bcMobFormat"][value="${format}"]`);
        if (styleRadio) styleRadio.checked = true;
        if (formatRadio) formatRadio.checked = true;
        setValue('bcMobBarcodeFmt', byId('lgBarcodeFmt')?.value || 'CODE128');
        setValue('bcMobCustomCode', byId('lgCustomCode')?.value || '');
        setValue('bcMobCustomQr', byId('lgCustomQr')?.value || '');
        setValue('bcMobCWidth', byId('lgCWidth')?.value || '60');
        setValue('bcMobCHeight', byId('lgCHeight')?.value || '37');
        setValue('bcMobCCols', byId('lgCCols')?.value || '3');
        setValue('bcMobCRows', byId('lgCRows')?.value || '7');
        setChecked('bcMobShowName', byId('lgShowName')?.checked ?? true);
        setChecked('bcMobShowSku', byId('lgShowSku')?.checked ?? true);
        setChecked('bcMobShowCode', byId('lgShowCode')?.checked ?? true);
        setChecked('bcMobShowImage', byId('lgShowImage')?.checked ?? true);
        setChecked('bcMobShowSize', byId('lgShowSize')?.checked ?? false);
    }

    function productMatchesWarehouse(product, warehouseId) {
        if (!warehouseId) return true;
        return Page._inWh ? Page._inWh(product, warehouseId) : true;
    }

    function categoriesForWarehouse(warehouseId) {
        const categories = new Set();
        (Page.prods || []).forEach((product) => {
            if (!productMatchesWarehouse(product, warehouseId)) return;
            if (product.category) categories.add(String(product.category));
        });
        return [...categories].sort((a, b) => a.localeCompare(b, 'fr', { sensitivity: 'base' }));
    }

    function warehouseName(id) {
        if (!id) return '';
        return (Page.whs || []).find((warehouse) => String(warehouse.id) === String(id))?.name || `Magasin #${id}`;
    }

    function getFilteredProducts() {
        const needle = String(state.search || '').trim().toLocaleLowerCase('fr-FR');
        return (Page.prods || [])
            .filter((product) => {
                if (!productMatchesWarehouse(product, state.warehouse)) return false;
                if (state.category && String(product.category || '') !== state.category) return false;
                if (!needle) return true;
                const haystack = [
                    product.name,
                    product.sku,
                    product.barcode,
                    product.category,
                    ...(Array.isArray(product.eans) ? product.eans : [])
                ].filter(Boolean).join(' ').toLocaleLowerCase('fr-FR');
                return haystack.includes(needle);
            })
            .sort((a, b) => String(a.name || '').localeCompare(String(b.name || ''), 'fr', { sensitivity: 'base' }));
    }

    function getStockLabel(product) {
        if (!state.warehouse) return '';
        const row = (Page.stock || []).find((item) => String(item.productId) === String(product.id)
            && String(item.warehouseId) === String(state.warehouse));
        if (!row) return 'Stock magasin : 0';
        const quantity = number(row.quantity, 0);
        return `Stock magasin : ${Number.isInteger(quantity) ? quantity : quantity.toLocaleString('fr-FR', { maximumFractionDigits: 2 })}`;
    }

    function updateFilterPresentation() {
        const chips = byId('bcMobActiveFilters');
        const counter = byId('bcMobFilterCount');
        const active = [];
        if (state.warehouse) active.push(warehouseName(state.warehouse));
        if (state.category) active.push(state.category);
        if (chips) chips.innerHTML = active.map((label) => `<span class="bc-mobile-filter-chip">${esc(label)}</span>`).join('');
        if (counter) {
            counter.hidden = active.length === 0;
            counter.textContent = String(active.length);
        }
    }

    function updateSelectionPresentation() {
        const totals = selectedTotals();
        const count = byId('bcMobSelectionCount');
        const next = byId('bcMobStep1Next');
        const summary = byId('bcMobStep1Summary');
        const clear = byId('bcMobClearSelection');
        if (count) count.textContent = `${totals.labels} étiquette${totals.labels > 1 ? 's' : ''}`;
        if (next) next.disabled = totals.labels < 1;
        if (summary) summary.textContent = totals.labels
            ? `${totals.products} produit${totals.products > 1 ? 's' : ''} · ${totals.labels} étiquette${totals.labels > 1 ? 's' : ''}`
            : 'Sélectionnez au moins un produit';
        if (clear) clear.hidden = totals.products === 0;
        saveState();
    }

    function productCard(product) {
        const selected = Page.selected.has(product.id);
        const qty = Page.selected.get(product.id) || 1;
        const image = Page._getImg ? Page._getImg(product) : '';
        const stock = getStockLabel(product);
        return `
            <article class="bc-mobile-product-card${selected ? ' is-selected' : ''}" data-product-id="${esc(product.id)}">
                <div class="bc-mobile-product-card__media">
                    ${image ? `<img src="${esc(image)}" alt="" loading="lazy" onerror="const host=this.parentElement;this.remove();if(host)host.textContent='📦'">` : '<span aria-hidden="true">📦</span>'}
                </div>
                <div class="bc-mobile-product-card__main">
                    <h4>${esc(product.name || 'Produit')}</h4>
                    <p>${esc(product.sku || product.barcode || 'Sans code')}</p>
                    ${stock ? `<span class="bc-mobile-product-card__stock">${esc(stock)}</span>` : ''}
                </div>
                <button type="button" class="bc-mobile-product-card__select" data-bc-action="toggle" aria-pressed="${selected ? 'true' : 'false'}" aria-label="${selected ? 'Retirer' : 'Sélectionner'} ${esc(product.name || 'ce produit')}">${selected ? '✓' : '+'}</button>
                ${selected ? `
                    <div class="bc-mobile-product-card__quantity">
                        <button type="button" data-bc-action="minus" aria-label="Retirer une étiquette">−</button>
                        <input type="number" min="1" max="100" value="${qty}" inputmode="numeric" data-bc-action="quantity" aria-label="Nombre d’étiquettes pour ${esc(product.name || 'ce produit')}">
                        <button type="button" data-bc-action="plus" aria-label="Ajouter une étiquette">+</button>
                        <span>${qty} étiquette${qty > 1 ? 's' : ''}</span>
                    </div>` : ''}
            </article>`;
    }

    function renderProducts(resetCount) {
        if (resetCount) state.renderCount = BATCH_SIZE;
        const list = byId('bcMobProductList');
        const more = byId('bcMobLoadMore');
        const results = byId('bcMobResultsCount');
        if (!list || !more) return;
        const filtered = getFilteredProducts();
        const visible = filtered.slice(0, state.renderCount);
        list.innerHTML = visible.length
            ? visible.map(productCard).join('')
            : '<div class="bc-mobile-empty">Aucun produit ne correspond à cette recherche.</div>';
        if (results) results.textContent = `${filtered.length} produit${filtered.length > 1 ? 's' : ''}`;
        more.innerHTML = filtered.length > visible.length
            ? `<button type="button" class="btn btn-secondary" data-bc-action="load-more">Afficher ${Math.min(BATCH_SIZE, filtered.length - visible.length)} produit${filtered.length - visible.length > 1 ? 's' : ''} de plus</button>`
            : '';
        updateFilterPresentation();
        updateSelectionPresentation();
    }

    function setProductQuantity(productId, quantity) {
        const product = (Page.prods || []).find((item) => String(item.id) === String(productId));
        if (!product) return;
        const qty = Math.max(0, Math.min(100, int(quantity, 0)));
        if (qty <= 0) Page.selected.delete(product.id);
        else Page.selected.set(product.id, qty);
        state.reviewPage = 1;
        renderProducts(false);
        renderStylePreview();
    }

    function selectVisible() {
        getFilteredProducts().forEach((product) => {
            if (!Page.selected.has(product.id)) Page.selected.set(product.id, 1);
        });
        state.reviewPage = 1;
        renderProducts(false);
        renderStylePreview();
        notify('Produits visibles sélectionnés', 'success');
    }

    function clearSelection(withToast) {
        Page.selected.clear();
        state.reviewPage = 1;
        renderProducts(false);
        renderStylePreview();
        if (withToast) notify('Sélection vidée', 'info');
    }

    function openFilters(trigger) {
        const content = document.createElement('form');
        content.className = 'bc-mobile-filter-form';
        content.innerHTML = `
            <label>Magasin
                <select class="sel" id="bcMobFilterWarehouse">
                    <option value="">Tous les magasins</option>
                    ${(Page.whs || []).map((warehouse) => `<option value="${esc(warehouse.id)}">${esc(warehouse.name || `Magasin #${warehouse.id}`)}</option>`).join('')}
                </select>
            </label>
            <label>Catégorie
                <select class="sel" id="bcMobFilterCategory"><option value="">Toutes les catégories</option></select>
            </label>
            <div class="bc-mobile-filter-form__actions">
                <button type="button" class="btn btn-secondary" data-filter-action="reset">Réinitialiser</button>
                <button type="submit" class="btn btn-primary">Appliquer les filtres</button>
            </div>`;

        const warehouse = content.querySelector('#bcMobFilterWarehouse');
        const category = content.querySelector('#bcMobFilterCategory');
        warehouse.value = state.warehouse;

        const fillCategories = () => {
            const values = categoriesForWarehouse(warehouse.value);
            const previous = category.value || state.category;
            category.innerHTML = '<option value="">Toutes les catégories</option>'
                + values.map((name) => `<option value="${esc(name)}">${esc(name)}</option>`).join('');
            category.value = values.includes(previous) ? previous : '';
        };
        fillCategories();
        warehouse.addEventListener('change', fillCategories);

        const apply = () => {
            state.warehouse = warehouse.value;
            state.category = category.value;
            state.renderCount = BATCH_SIZE;
            renderProducts(true);
            saveState();
            state.filterHandle?.close?.('apply');
        };
        content.addEventListener('submit', (event) => {
            event.preventDefault();
            apply();
        });
        content.querySelector('[data-filter-action="reset"]')?.addEventListener('click', () => {
            warehouse.value = '';
            state.category = '';
            fillCategories();
            category.value = '';
        });

        if (global.FilterDrawer?.open) {
            state.filterHandle = global.FilterDrawer.open({
                title: 'Filtres des produits',
                description: 'Limitez la sélection à un magasin ou une catégorie.',
                content,
                trigger
            });
        } else if (global.ActionSheet?.open) {
            state.filterHandle = global.ActionSheet.open({ title: 'Filtres des produits', content, trigger });
        }
    }

    function currentStyle() {
        return getSelectedRadio('bcMobStyle', 'img_barcode');
    }

    function currentFormat() {
        return getSelectedRadio('bcMobFormat', 'A4_21');
    }

    function styleLabel(style) {
        return ({
            img_barcode: 'Image + code-barres',
            barcode: 'Code-barres seul',
            qr: 'QR Code',
            full: 'Étiquette complète'
        })[style] || style;
    }

    function updateStyleFields() {
        const qr = currentStyle() === 'qr';
        const barcodeFields = byId('bcMobBarcodeFields');
        const qrFields = byId('bcMobQrFields');
        const formatControl = byId('bcMobBarcodeFmt');
        if (barcodeFields) barcodeFields.hidden = qr;
        if (qrFields) qrFields.hidden = !qr;
        if (formatControl) formatControl.disabled = qr;
        renderStylePreview();
    }

    function validateCodes(showMessage) {
        syncToDesktop();
        const validation = byId('bcMobCodeValidation');
        let message = '';
        if (currentStyle() !== 'qr') {
            const format = byId('bcMobBarcodeFmt')?.value || 'CODE128';
            for (const [id] of Page.selected) {
                const product = (Page.prods || []).find((item) => String(item.id) === String(id));
                if (!product) continue;
                const code = Page._getCode(product);
                const valid = Page._validateCode(code, format);
                if (!valid.ok) {
                    message = `${product.name || 'Produit'} : ${valid.msg}`;
                    break;
                }
                const fit = Page._assessCodeFit(code, format);
                if (fit.level === 'block') {
                    message = `${product.name || 'Produit'} : ${fit.msg}`;
                    break;
                }
            }
        }
        if (validation) {
            validation.hidden = !message || !showMessage;
            validation.textContent = message;
        }
        return message;
    }

    function expandedLabels() {
        const labels = [];
        Page.selected.forEach((qty, id) => {
            const product = (Page.prods || []).find((item) => String(item.id) === String(id));
            if (!product) return;
            for (let index = 0; index < Math.max(1, int(qty, 1)); index += 1) labels.push(product);
        });
        return labels;
    }

    function renderGeneratedLabels(container, products, prefix) {
        if (!container) return;
        syncToDesktop();
        if (!products.length) {
            container.innerHTML = '<div class="bc-mobile-empty">Ajoutez un produit pour afficher l’aperçu.</div>';
            return;
        }
        const ids = [];
        container.innerHTML = products.map((product, index) => {
            const id = `${prefix}-${index}-${Date.now()}`;
            ids.push([product, id]);
            return Page._renderLabel(product, id, 'preview');
        }).join('');
        global.setTimeout(() => {
            ids.forEach(([product, id]) => Page._generateBarcode(product, id, true));
        }, 30);
    }

    function renderStylePreview() {
        syncToDesktop();
        updateFormatSummary();
        const first = expandedLabels()[0];
        const target = byId('bcMobStylePreview');
        const meta = byId('bcMobStylePreviewMeta');
        if (meta) meta.textContent = styleLabel(currentStyle());
        renderGeneratedLabels(target, first ? [first] : [], 'bc-style-preview');
    }

    function updateFormatVisibility() {
        const custom = currentFormat() === 'custom';
        const customBox = byId('bcMobCustomFormat');
        if (customBox) customBox.hidden = !custom;
        updateFormatSummary();
    }

    function formatDefinition() {
        syncToDesktop();
        return Page._getFmt();
    }

    function updateFormatSummary() {
        syncToDesktop();
        const target = byId('bcMobFormatSummary');
        if (!target) return;
        const fmt = Page._getFmt();
        const perPage = Math.max(1, int(fmt.cols, 1) * int(fmt.rows, 1));
        const totals = selectedTotals();
        const pages = totals.labels ? Math.ceil(totals.labels / perPage) : 0;
        target.innerHTML = `<strong>${esc(fmt.title || 'Format')}</strong><br>${fmt.wmm} × ${fmt.hmm} mm · ${perPage} étiquette${perPage > 1 ? 's' : ''} par page${pages ? ` · ${pages} page${pages > 1 ? 's' : ''} prévue${pages > 1 ? 's' : ''}` : ''}.`;
    }

    function validateCustomFormat(showMessage) {
        const validation = byId('bcMobFormatValidation');
        let message = '';
        if (currentFormat() === 'custom') {
            const width = number(byId('bcMobCWidth')?.value, 0);
            const height = number(byId('bcMobCHeight')?.value, 0);
            const cols = int(byId('bcMobCCols')?.value, 0);
            const rows = int(byId('bcMobCRows')?.value, 0);
            if (width < 20 || width > 190) message = 'La largeur personnalisée doit être comprise entre 20 et 190 mm.';
            else if (height < 15 || height > 270) message = 'La hauteur personnalisée doit être comprise entre 15 et 270 mm.';
            else if (cols < 1 || cols > 8 || rows < 1 || rows > 20) message = 'Le nombre de colonnes ou de lignes est invalide.';
            else if ((cols * width) > 210) message = 'La largeur totale des colonnes dépasse la largeur de la page A4.';
            else if ((rows * height) > 297) message = 'La hauteur totale des lignes dépasse la hauteur de la page A4.';
        }
        if (validation && showMessage) {
            validation.hidden = !message;
            validation.textContent = message;
        }
        return message;
    }

    function reviewRows(items) {
        return items.map(([label, value]) => `<div><dt>${esc(label)}</dt><dd>${esc(value)}</dd></div>`).join('');
    }

    function renderReview() {
        syncToDesktop();
        const totals = selectedTotals();
        const fmt = Page._getFmt();
        const perPage = Math.max(1, int(fmt.cols, 1) * int(fmt.rows, 1));
        const pages = Math.max(1, Math.ceil(totals.labels / perPage));
        const selection = byId('bcMobReviewSelection');
        const rendering = byId('bcMobReviewRendering');
        if (selection) selection.innerHTML = reviewRows([
            ['Produits', String(totals.products)],
            ['Étiquettes', String(totals.labels)],
            ['Magasin', state.warehouse ? warehouseName(state.warehouse) : 'Tous'],
            ['Catégorie', state.category || 'Toutes']
        ]);
        if (rendering) rendering.innerHTML = reviewRows([
            ['Style', styleLabel(currentStyle())],
            ['Format', fmt.title || currentFormat()],
            ['Planche', `${perPage} par page`],
            ['Pages prévues', String(pages)]
        ]);
        state.reviewPage = Math.max(1, Math.min(pages, state.reviewPage));
        renderReviewPreview();
        validateCodes(true);
        validateCustomFormat(true);
        saveState();
    }

    function renderReviewPreview() {
        syncToDesktop();
        const labels = expandedLabels();
        const fmt = Page._getFmt();
        const perPage = Math.max(1, int(fmt.cols, 1) * int(fmt.rows, 1));
        const totalPages = Math.max(1, Math.ceil(labels.length / perPage));
        state.reviewPage = Math.max(1, Math.min(totalPages, state.reviewPage));
        const pageLabels = labels.slice((state.reviewPage - 1) * perPage, state.reviewPage * perPage);
        const sample = pageLabels.slice(0, PREVIEW_LIMIT);
        const target = byId('bcMobReviewPreview');
        const page = byId('bcMobPreviewPage');
        const title = byId('bcMobReviewPreviewTitle');
        const note = byId('bcMobPreviewNote');
        const prev = byId('bcMobPreviewPrev');
        const next = byId('bcMobPreviewNext');
        if (page) page.textContent = `${state.reviewPage} / ${totalPages}`;
        if (title) title.textContent = `Page ${state.reviewPage}`;
        if (prev) prev.disabled = state.reviewPage <= 1;
        if (next) next.disabled = state.reviewPage >= totalPages;
        if (note) {
            note.textContent = pageLabels.length > PREVIEW_LIMIT
                ? `${pageLabels.length} étiquettes sur cette page. L’aperçu tactile affiche les ${PREVIEW_LIMIT} premières ; l’impression contient la page complète.`
                : `${pageLabels.length} étiquette${pageLabels.length > 1 ? 's' : ''} sur cette page.`;
        }
        renderGeneratedLabels(target, sample, `bc-review-${state.reviewPage}`);
    }

    function validateOutput() {
        const totals = selectedTotals();
        const validation = byId('bcMobOutputValidation');
        let message = '';
        if (!totals.labels) message = 'Sélectionnez au moins un produit.';
        if (!message) message = validateCodes(false);
        if (!message) {
            const customFormat = currentFormat() === 'custom';
            if (customFormat) {
                const width = number(byId('bcMobCWidth')?.value, 0);
                const height = number(byId('bcMobCHeight')?.value, 0);
                const cols = int(byId('bcMobCCols')?.value, 0);
                const rows = int(byId('bcMobCRows')?.value, 0);
                if (width < 20 || width > 190 || height < 15 || height > 270 || cols < 1 || cols > 8 || rows < 1 || rows > 20 || cols * width > 210 || rows * height > 297) {
                    message = 'Le format personnalisé doit être corrigé avant la sortie.';
                }
            }
        }
        if (validation) {
            validation.hidden = !message;
            validation.textContent = message;
        }
        if (message) announce(message, 'assertive');
        return !message;
    }

    async function validateStep({ stepIndex }) {
        if (stepIndex === 0) {
            if (selectedTotals().labels < 1) {
                announce('Sélectionnez au moins un produit avant de continuer.', 'assertive');
                notify('Sélectionnez au moins un produit', 'warning');
                return false;
            }
            return true;
        }
        if (stepIndex === 1) {
            const error = validateCodes(true);
            if (error) {
                announce(error, 'assertive');
                return false;
            }
            return true;
        }
        if (stepIndex === 2) {
            const error = validateCustomFormat(true);
            if (error) {
                announce(error, 'assertive');
                return false;
            }
            return true;
        }
        return true;
    }

    function onFlowChange({ index }) {
        syncToDesktop();
        if (index === 0) renderProducts(false);
        else if (index === 1) renderStylePreview();
        else if (index === 2) updateFormatSummary();
        else if (index === 3) renderReview();
        state.flow?.persist?.();
        saveState();
    }

    function resetAll() {
        const confirmed = Page.selected.size === 0 || global.confirm('Vider la sélection et réinitialiser la préparation ?');
        if (!confirmed) return;
        Page.selected.clear();
        state.search = '';
        state.warehouse = '';
        state.category = '';
        state.renderCount = BATCH_SIZE;
        state.reviewPage = 1;
        clearSavedState();
        const flowRoot = root();
        flowRoot?.querySelectorAll('input, select, textarea').forEach((control) => {
            if (control.type === 'radio' || control.type === 'checkbox') return;
            if (control.id === 'bcMobSearch') control.value = '';
        });
        const style = document.querySelector('input[name="bcMobStyle"][value="img_barcode"]');
        const format = document.querySelector('input[name="bcMobFormat"][value="A4_21"]');
        if (style) style.checked = true;
        if (format) format.checked = true;
        setValue('bcMobBarcodeFmt', 'CODE128');
        setValue('bcMobCustomCode', '');
        setValue('bcMobCustomQr', '');
        setValue('bcMobCWidth', '60');
        setValue('bcMobCHeight', '37');
        setValue('bcMobCCols', '3');
        setValue('bcMobCRows', '7');
        setChecked('bcMobShowName', true);
        setChecked('bcMobShowSku', true);
        setChecked('bcMobShowCode', true);
        setChecked('bcMobShowImage', true);
        setChecked('bcMobShowSize', false);
        state.flow?.clearDraft?.();
        state.flow?.goTo?.(0, { validate: false });
        updateStyleFields();
        updateFormatVisibility();
        renderProducts(true);
        notify('Préparation réinitialisée', 'info');
    }

    function bindProductList() {
        const list = byId('bcMobProductList');
        if (!list || list.dataset.bound === '1') return;
        list.dataset.bound = '1';
        list.addEventListener('click', (event) => {
            const action = event.target.closest('[data-bc-action]');
            if (!action) return;
            const card = action.closest('[data-product-id]');
            if (!card) return;
            const id = card.dataset.productId;
            const product = (Page.prods || []).find((item) => String(item.id) === String(id));
            if (!product) return;
            const current = Page.selected.get(product.id) || 0;
            if (action.dataset.bcAction === 'toggle') setProductQuantity(product.id, current ? 0 : 1);
            else if (action.dataset.bcAction === 'minus') setProductQuantity(product.id, current - 1);
            else if (action.dataset.bcAction === 'plus') setProductQuantity(product.id, current + 1);
        });
        list.addEventListener('change', (event) => {
            const input = event.target.closest('[data-bc-action="quantity"]');
            if (!input) return;
            const card = input.closest('[data-product-id]');
            if (card) setProductQuantity(card.dataset.productId, input.value);
        });
    }

    function bindControls() {
        const search = byId('bcMobSearch');
        if (search) {
            search.value = state.search;
            search.addEventListener('input', () => {
                state.search = search.value;
                global.clearTimeout(state.renderTimer);
                state.renderTimer = global.setTimeout(() => renderProducts(true), 100);
            });
        }
        byId('bcMobOpenFilters')?.addEventListener('click', (event) => openFilters(event.currentTarget));
        byId('bcMobSelectVisible')?.addEventListener('click', selectVisible);
        byId('bcMobClearSelection')?.addEventListener('click', () => clearSelection(true));
        byId('bcMobClear')?.addEventListener('click', resetAll);
        byId('bcMobLoadMore')?.addEventListener('click', (event) => {
            if (!event.target.closest('[data-bc-action="load-more"]')) return;
            state.renderCount += BATCH_SIZE;
            renderProducts(false);
            saveState();
        });

        document.querySelectorAll('input[name="bcMobStyle"]').forEach((radio) => {
            radio.addEventListener('change', updateStyleFields);
        });
        ['bcMobBarcodeFmt', 'bcMobCustomCode', 'bcMobCustomQr'].forEach((id) => {
            byId(id)?.addEventListener('input', () => {
                validateCodes(false);
                renderStylePreview();
            });
            byId(id)?.addEventListener('change', () => {
                validateCodes(true);
                renderStylePreview();
            });
        });
        document.querySelectorAll('input[name="bcMobFormat"]').forEach((radio) => {
            radio.addEventListener('change', updateFormatVisibility);
        });
        ['bcMobCWidth', 'bcMobCHeight', 'bcMobCCols', 'bcMobCRows',
            'bcMobShowName', 'bcMobShowSku', 'bcMobShowCode', 'bcMobShowImage', 'bcMobShowSize']
            .forEach((id) => {
                byId(id)?.addEventListener('input', () => {
                    updateFormatSummary();
                    renderStylePreview();
                });
                byId(id)?.addEventListener('change', () => {
                    updateFormatSummary();
                    renderStylePreview();
                });
            });

        byId('bcMobPreviewPrev')?.addEventListener('click', () => {
            state.reviewPage = Math.max(1, state.reviewPage - 1);
            renderReviewPreview();
            saveState();
        });
        byId('bcMobPreviewNext')?.addEventListener('click', () => {
            state.reviewPage += 1;
            renderReviewPreview();
            saveState();
        });
        byId('bcMobPrint')?.addEventListener('click', () => {
            if (!validateOutput()) return;
            syncToDesktop();
            Page.doPrint();
        });
        byId('bcMobPdf')?.addEventListener('click', async () => {
            if (!validateOutput()) return;
            syncToDesktop();
            const button = byId('bcMobPdf');
            if (button) button.disabled = true;
            try { await Page.exportPDF(); }
            finally { if (button) button.disabled = false; }
        });
    }

    function refreshDesktopAfterDeviceChange() {
        if (isAdaptive()) {
            renderProducts(false);
            const index = state.flow?.current ?? 0;
            onFlowChange({ index });
            return;
        }
        syncToDesktop();
        Page.renderProductGrid();
        Page.renderPreview();
        Page._updateSelBadge();
    }

    async function initAfterPage() {
        if (state.initialized || global.SM_PAGE !== 'barcodes' || !root()) return;
        state.initialized = true;
        document.documentElement.dataset.barcodesMobileWizard = VERSION;
        restoreState();
        syncFromDesktopDefaults();
        bindProductList();
        bindControls();
        if (!global.MobileFlow?.create) {
            console.error('[barcodes-mobile] MobileFlow indisponible.');
            return;
        }
        state.flow = global.MobileFlow.create(root(), {
            storageKey: FLOW_KEY,
            validateStep,
            onChange: onFlowChange
        });
        state.search = byId('bcMobSearch')?.value || state.search;
        updateStyleFields();
        updateFormatVisibility();
        renderProducts(false);
        onFlowChange({ index: state.flow.current || 0 });
        global.addEventListener('magestock:devicechange', refreshDesktopAfterDeviceChange);
        announce('Assistant de génération des étiquettes prêt.');
    }

    const originalInit = Page.init.bind(Page);
    Page.init = async function barcodesResponsiveInit() {
        await originalInit();
        await initAfterPage();
    };

    global.MageStockBarcodesMobile = {
        version: VERSION,
        init: initAfterPage,
        renderProducts: () => renderProducts(true),
        renderReview,
        clear: resetAll
    };
})(window, document);
