import {
    INGREDIENTS,
    PRODUCTS,
    CATEGORY_LABELS,
    getProductsForIngredient,
    ingredientHasProducts,
    pantryQtyStep,
} from '../data/catalog.js?v=9';
import {
    addOrMergeShoppingLines,
    KITCHEN_LIST_ID,
    loadPantry,
    loadShoppingState,
    removeItemFromList,
    setPantryQty,
    setPantryProductQty,
    getPantryTotal,
    getPantryProducts,
    updateKitchenItemAmount,
} from '../services/pantryShopping.js?v=2';
import { showAppToast } from './toast.js';

const CATEGORY_ICONS = {
    pieczywo: 'fa-bread-slice',
    nabial: 'fa-cheese',
    mieso_ryby: 'fa-drumstick-bite',
    warzywa: 'fa-carrot',
    owoce: 'fa-apple-whole',
    suche: 'fa-wheat-awn',
    przyprawy: 'fa-leaf',
    inne: 'fa-jar',
};

function esc(s) {
    return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}

function unitLabel(u) {
    return u === 'szt' ? 'szt.' : u;
}

function formatQty(n) {
    const rounded = Math.round((Number(n) || 0) * 10) / 10;
    return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1).replace(/\.0$/, '');
}

function formatQtyWithUnit(qty, unit) {
    return `${formatQty(qty)} ${unitLabel(unit)}`;
}

function productCountLabel(count) {
    if (count === 1) return '1 produkt';
    const mod10 = count % 10;
    const mod100 = count % 100;
    if (mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14)) return `${count} produkty`;
    return `${count} produktów`;
}

function nutritionForQty(def, qty, nutrition = def?.nutritionPer100g) {
    if (!def || !nutrition || !Number.isFinite(qty) || qty <= 0) return null;
    let grams = qty;
    if (def.pantryUnit === 'szt' && def.weightPerPiece) grams = qty * def.weightPerPiece;
    const factor = grams / 100;
    return {
        kcal: Math.round(nutrition.kcal * factor),
        protein: Math.round(nutrition.protein * factor * 10) / 10,
        fat: Math.round(nutrition.fat * factor * 10) / 10,
        carbs: Math.round(nutrition.carbs * factor * 10) / 10,
    };
}

function macroLine(n) {
    if (!n) return '';
    return `${n.kcal} kcal · ${formatQty(n.protein)}g B · ${formatQty(n.fat)}g T · ${formatQty(n.carbs)}g W`;
}

function mediaHtml(image, icon, sizeClass = 'w-9 h-9', radiusClass = 'rounded-lg') {
    if (image) {
        const fit = image.endsWith('.svg') ? 'object-contain' : 'object-cover';
        return `<img src="${esc(image)}" alt="" class="${sizeClass} ${radiusClass} ${fit} shrink-0">`;
    }
    return `<div class="${sizeClass} ${radiusClass} flex items-center justify-center shrink-0" style="background:rgb(var(--card-soft-rgb));"><i class="fas ${icon} text-sm" style="color:rgb(var(--text-faint-rgb));"></i></div>`;
}

function compactMetaText(text, tone = 'default') {
    const color = tone === 'success' ? 'rgb(var(--success-rgb))' : tone === 'muted' ? 'rgb(var(--text-dim-rgb))' : 'rgb(var(--text-body-soft-rgb))';
    return `<span class="text-[10px] font-medium" style="color:${color};">${esc(text)}</span>`;
}

function sortProductsByStock(products, pantryItems) {
    return [...products].sort((a, b) => {
        const aq = pantryItems.find((i) => i.productId === a.id)?.qty || 0;
        const bq = pantryItems.find((i) => i.productId === b.id)?.qty || 0;
        if (bq !== aq) return bq - aq;
        return a.name.localeCompare(b.name, 'pl');
    });
}

function formatPackAwareAmount(amount, pantryUnit, packSize, packLabel) {
    const qty = Number(amount) || 0;
    const unit = unitLabel(pantryUnit);
    if (packSize && packSize > 0) {
        const packs = qty / packSize;
        if (qty > 0 && Number.isFinite(packs) && Math.abs(packs - Math.round(packs)) < 0.001) {
            return `${formatQty(Math.round(packs))} x ${packLabel || `${formatQty(packSize)} ${unit}`}`;
        }
    }
    return `${formatQty(qty)} ${unit}`;
}

function normalizeQty(value) {
    return Math.max(0, Math.round((Number(value) || 0) * 100) / 100);
}

function formatPreciseQty(n) {
    const rounded = Math.round((Number(n) || 0) * 1000) / 1000;
    if (Number.isInteger(rounded)) return String(rounded);
    return rounded.toFixed(3).replace(/0+$/, '').replace(/\.$/, '');
}

function formatPackCount(amount, packSize) {
    if (!Number.isFinite(Number(packSize)) || Number(packSize) <= 0) return '';
    return `${formatPreciseQty((Number(amount) || 0) / Number(packSize))} opak.`;
}

function getQtyStepMeta(def, product = null) {
    const productPackSize = Number(product?.packSize);
    if (Number.isFinite(productPackSize) && productPackSize > 0) {
        return {
            step: productPackSize,
            usesPackStep: true,
            stepLabel: product?.packLabel || formatQtyWithUnit(productPackSize, def.pantryUnit),
        };
    }

    const ingredientPackSize = Number(def?.purchasePack?.amount);
    if (Number.isFinite(ingredientPackSize) && ingredientPackSize > 0) {
        return {
            step: ingredientPackSize,
            usesPackStep: true,
            stepLabel: def.purchasePack?.label || formatQtyWithUnit(ingredientPackSize, def.pantryUnit),
        };
    }

    return {
        step: pantryQtyStep(def.id),
        usesPackStep: false,
        stepLabel: '',
    };
}

export function getIngredientCardHTML({
    idBase,
    overlayClass = 'fixed inset-0 z-[70] hidden opacity-0 transition-opacity duration-200 flex items-center justify-center p-5',
    overlayStyle = 'pointer-events:none; background:rgba(var(--overlay-rgb),0.5);',
    cardClass = 'relative w-full max-w-xs rounded-2xl shadow-2xl overflow-hidden',
    cardStyle = 'background:rgb(var(--app-bg-rgb)); pointer-events:auto; max-height:85vh; overflow-y:auto; transform:translateY(0.75rem); opacity:0; transition:transform 220ms ease, opacity 220ms ease;',
} = {}) {
    if (!idBase) throw new Error('getIngredientCardHTML requires idBase');
    return `
    <div id="${idBase}-overlay" class="${overlayClass}" style="${overlayStyle}">
        <div id="${idBase}" class="${cardClass}" style="${cardStyle}">
            <div class="relative px-4 pt-4 pb-2">
                <div class="flex items-start gap-3 pr-10">
                    <div id="${idBase}-hero" class="relative w-20 h-20 rounded-2xl flex items-center justify-center shrink-0 overflow-hidden" style="background:rgb(var(--card-rgb));">
                        <img id="${idBase}-img" class="w-full h-full hidden" alt="" />
                        <div id="${idBase}-fallback" class="absolute inset-0 flex items-center justify-center">
                            <i id="${idBase}-fallback-icon" class="fas fa-box-open text-2xl" style="color:rgb(var(--text-subdued-rgb));"></i>
                        </div>
                    </div>
                    <div class="flex-1 min-w-0 pt-0.5">
                        <div class="flex items-center gap-1.5">
                            <button type="button" id="${idBase}-back" class="hidden w-5 h-5 rounded-full items-center justify-center shrink-0" style="background:rgb(var(--card-rgb)); color:rgb(var(--text-body-rgb));" aria-label="Wróć do składnika">
                                <i class="fas fa-chevron-left text-[9px]"></i>
                            </button>
                            <p id="${idBase}-category" class="text-[10px] font-semibold uppercase tracking-wider truncate" style="color:rgb(var(--success-rgb));"></p>
                        </div>
                        <h3 id="${idBase}-name" class="text-[15px] font-bold leading-snug mt-0.5" style="color:rgb(var(--text-body-rgb));"></h3>
                        <p id="${idBase}-subtitle" class="text-[11px] mt-0.5 hidden" style="color:rgb(var(--text-dim-rgb));"></p>
                    </div>
                </div>
                <button type="button" id="${idBase}-close" class="absolute top-3 right-3 w-8 h-8 rounded-full flex items-center justify-center" style="background:rgb(var(--card-rgb)); color:rgb(var(--text-body-rgb));" aria-label="Zamknij">
                    <i class="fas fa-times text-sm"></i>
                </button>
            </div>
            <div class="px-4 pt-2 pb-4 space-y-3">
                <div id="${idBase}-nutrition"></div>
                <div id="${idBase}-stock"></div>
                <div id="${idBase}-products"></div>
                <div id="${idBase}-shop"></div>
            </div>
        </div>
    </div>`;
}

export function createIngredientCardController({ idBase, defaultSourceNote = 'Ze spiżarni' } = {}) {
    if (!idBase) throw new Error('createIngredientCardController requires idBase');

    const state = {
        ingredientId: null,
        productId: null,
        selectedProductId: null,
        allowProductSelection: true,
        sourceNote: defaultSourceNote,
        onProductChange: null,
        onAfterChange: null,
        stockEditorOpen: false,
        stockDraftQty: null,
        shopEditorOpen: false,
        shopDraftQty: null,
        closeTimer: null,
    };
    let bound = false;

    const el = (suffix = '') => document.getElementById(suffix ? `${idBase}-${suffix}` : idBase);

    function resetInlineEditors() {
        state.stockEditorOpen = false;
        state.stockDraftQty = null;
        state.shopEditorOpen = false;
        state.shopDraftQty = null;
    }

    function getShoppingItemFor(def, product) {
        const shopping = loadShoppingState();
        const kitchen = shopping.lists.find((list) => list.id === KITCHEN_LIST_ID && list.type === 'kitchen');
        if (!kitchen || kitchen.type !== 'kitchen') return null;
        const unit = unitLabel(def.pantryUnit);
        const targetProductId = product?.id || '';
        return kitchen.items.find((item) => {
            if (item.checked) return false;
            return item.ingredientId === def.id
                && item.unit === unit
                && (item.productId || '') === targetProductId;
        }) || null;
    }

    function renderHeader(def, product, pantry) {
        const hasProducts = ingredientHasProducts(def.id);
        const isListMode = hasProducts && state.allowProductSelection && !state.productId;
        const isBackAvailable = hasProducts && state.allowProductSelection && state.productId;
        const icon = CATEGORY_ICONS[def.category] || 'fa-jar';
        const heroEl = el('hero');
        const img = el('img');
        const fallback = el('fallback');
        const fallbackIcon = el('fallback-icon');

        if (img && fallback) {
            const image = isListMode ? def.image : (product?.image || def.image);
            const altName = isListMode ? def.name : (product?.name || def.name);
            if (image) {
                img.src = image;
                img.alt = altName;
                const isSvg = image.endsWith('.svg');
                img.classList.toggle('object-contain', isSvg);
                img.classList.toggle('object-cover', !isSvg);
                img.style.padding = isSvg ? '4px' : '';
                img.classList.remove('hidden');
                fallback.classList.add('hidden');
            } else {
                img.classList.add('hidden');
                fallback.classList.remove('hidden');
                if (fallbackIcon) fallbackIcon.className = `fas ${icon} text-2xl`;
            }
        }

        const categoryEl = el('category');
        const nameEl = el('name');
        const subtitleEl = el('subtitle');
        const backBtn = el('back');

        const displayProduct = isListMode ? null : product;
        if (categoryEl) categoryEl.textContent = displayProduct?.brand || CATEGORY_LABELS[def.category] || def.category;
        if (nameEl) nameEl.textContent = displayProduct?.name || def.name;

        if (subtitleEl) {
            let subtitle = '';
            if (displayProduct) {
                subtitle = [def.name, displayProduct.packLabel].filter(Boolean).join(' • ');
            } else if (!hasProducts && def.purchasePack?.label) {
                subtitle = def.purchasePack.label;
            }

            if (subtitle) {
                subtitleEl.textContent = subtitle;
                subtitleEl.classList.remove('hidden');
            } else {
                subtitleEl.classList.add('hidden');
            }
        }

        if (backBtn) {
            backBtn.classList.toggle('hidden', !isBackAvailable);
            backBtn.classList.toggle('flex', Boolean(isBackAvailable));
        }
    }

    function renderNutrition(def, product) {
        const wrap = el('nutrition');
        if (!wrap) return;
        const hasProducts = ingredientHasProducts(def.id);
        const isListMode = hasProducts && state.allowProductSelection && !state.productId;
        if (isListMode) {
            wrap.innerHTML = '';
            return;
        }
        const nutrition = product?.nutritionPer100g || def.nutritionPer100g;
        if (!nutrition) {
            wrap.innerHTML = '';
            return;
        }

        const unitScope = def.pantryUnit === 'ml' ? 'na 100 ml' : 'na 100 g';
        const hint = product
            ? ''
            : hasProducts
                ? 'orientacyjnie dla składnika'
                : '';
        const nutritionMeta = hint ? `${unitScope} • ${hint}` : unitScope;

        wrap.innerHTML = `
            <p class="text-[9px] font-semibold uppercase tracking-wide mb-1.5" style="color:rgb(var(--text-dim-rgb));">Wartości odżywcze</p>
            <p class="text-[10px] mb-1.5" style="color:rgb(var(--text-dim-rgb));">${esc(nutritionMeta)}</p>
            <div class="grid grid-cols-4 gap-1.5">
                <div class="rounded-xl px-2 py-1.5 text-center" style="background:rgb(var(--card-rgb));">
                    <p class="text-[15px] font-bold tabular-nums leading-tight" style="color:rgb(var(--text-body-rgb));">${nutrition.kcal}</p>
                    <p class="text-[9px] font-medium" style="color:rgb(var(--text-dim-rgb));">kcal</p>
                </div>
                <div class="rounded-xl px-2 py-1.5 text-center" style="background:rgb(var(--card-rgb));">
                    <p class="text-[15px] font-bold text-blue-400 tabular-nums leading-tight">${formatQty(nutrition.protein)}g</p>
                    <p class="text-[9px] font-medium" style="color:rgb(var(--text-dim-rgb));">białko</p>
                </div>
                <div class="rounded-xl px-2 py-1.5 text-center" style="background:rgb(var(--card-rgb));">
                    <p class="text-[15px] font-bold text-amber-400 tabular-nums leading-tight">${formatQty(nutrition.fat)}g</p>
                    <p class="text-[9px] font-medium" style="color:rgb(var(--text-dim-rgb));">tłuszcz</p>
                </div>
                <div class="rounded-xl px-2 py-1.5 text-center" style="background:rgb(var(--card-rgb));">
                    <p class="text-[15px] font-bold text-orange-400 tabular-nums leading-tight">${formatQty(nutrition.carbs)}g</p>
                    <p class="text-[9px] font-medium" style="color:rgb(var(--text-dim-rgb));">węgl.</p>
                </div>
            </div>`;
    }

    function renderStockEditorInto(wrap, def, product, pantry) {
        const totalQty = getPantryTotal(def.id, pantry);
        const unit = unitLabel(def.pantryUnit);
        const qty = product
            ? (getPantryProducts(def.id, pantry).find((i) => i.productId === product.id)?.qty || 0)
            : totalQty;
        const { step, usesPackStep } = getQtyStepMeta(def, product);
        const packSize = product?.packSize || def.purchasePack?.amount || 0;
        const packLabel = product?.packLabel || def.purchasePack?.label || '';
        const draftQty = state.stockEditorOpen
            ? normalizeQty(state.stockDraftQty ?? qty)
            : qty;
        const stockValueLabel = usesPackStep
            ? formatPackCount(qty, step)
            : formatPackAwareAmount(qty, def.pantryUnit, packSize, packLabel);
        const stockSubLabel = usesPackStep ? formatQtyWithUnit(qty, def.pantryUnit) : '';
        const draftInputValue = usesPackStep
            ? formatPreciseQty(draftQty / step)
            : formatPreciseQty(draftQty);
        const draftInputUnit = usesPackStep ? 'opak.' : unit;
        const actionLabel = state.stockEditorOpen ? 'Anuluj' : 'Zmień';

        wrap.innerHTML = `
            <p class="text-[9px] font-semibold uppercase tracking-wide mb-1.5" style="color:rgb(var(--text-dim-rgb));">Zapas</p>
            <div class="rounded-2xl border px-3 py-3" style="background:rgb(var(--card-rgb)); border-color:rgb(var(--card-strong-rgb));">
                <div class="flex items-start justify-between gap-3">
                    <div class="min-w-0 flex-1">
                        <p class="text-[16px] font-bold tabular-nums" style="color:rgb(var(--success-rgb));">${esc(stockValueLabel)}</p>
                        ${stockSubLabel ? `<p class="text-[11px] mt-1" style="color:rgb(var(--text-dim-rgb));">${esc(stockSubLabel)}</p>` : ''}
                    </div>
                    <button type="button" class="ingredient-card-stock-toggle inline-flex items-center rounded-full px-2.5 py-1 text-[10px] font-semibold shrink-0" style="background:${state.stockEditorOpen ? 'rgb(var(--sunken-rgb))' : 'rgb(var(--card-soft-rgb))'}; color:${state.stockEditorOpen ? 'rgb(var(--text-emphasis-rgb))' : 'rgb(var(--text-body-soft-rgb))'};">
                        ${esc(actionLabel)}
                    </button>
                </div>
                ${state.stockEditorOpen ? `
                    <div class="mt-3 pt-3 border-t" style="border-color:rgb(var(--card-strong-rgb));">
                        <div class="flex items-center gap-2">
                            <button type="button" class="ingredient-card-stock-step w-9 h-9 rounded-xl flex items-center justify-center shrink-0" style="background:rgb(var(--card-soft-rgb)); color:rgb(var(--text-body-soft-rgb));" data-dir="-1" aria-label="Zmniejsz szkic zapasu">
                                <i class="fas fa-minus text-xs"></i>
                            </button>
                            <label class="flex-1 rounded-xl px-3 py-2 flex items-center justify-center gap-2" style="background:rgb(var(--card-soft-rgb));">
                                <input type="number" min="0" step="${usesPackStep ? '1' : step}" value="${draftInputValue}" class="ingredient-card-stock-input w-20 bg-transparent text-center text-[14px] font-semibold tabular-nums outline-none appearance-none" style="color:rgb(var(--text-body-rgb)); background:transparent !important; border:none !important; box-shadow:none !important; -webkit-appearance:none; -moz-appearance:textfield;">
                                <span class="text-[12px] font-medium shrink-0" style="color:rgb(var(--text-dim-rgb));">${esc(draftInputUnit)}</span>
                            </label>
                            <button type="button" class="ingredient-card-stock-step w-9 h-9 rounded-xl flex items-center justify-center shrink-0" style="background:rgb(var(--card-soft-rgb)); color:rgb(var(--text-body-soft-rgb));" data-dir="1" aria-label="Zwiększ szkic zapasu">
                                <i class="fas fa-plus text-xs"></i>
                            </button>
                        </div>
                        ${usesPackStep ? `<p class="text-[10px] mt-2 text-right" style="color:rgb(var(--text-dim-rgb));">${esc(formatQtyWithUnit(draftQty, def.pantryUnit))}</p>` : ''}
                        <div class="flex items-center justify-between gap-3 mt-3">
                            <button type="button" class="ingredient-card-stock-clear text-[11px] font-semibold" style="color:rgb(var(--text-dim-rgb));">Wyzeruj</button>
                            <button type="button" class="ingredient-card-stock-save inline-flex items-center rounded-full px-3 py-1.5 text-[11px] font-semibold" style="background:rgb(var(--text-body-rgb)); color:rgb(var(--app-bg-rgb));">Zapisz</button>
                        </div>
                    </div>` : ''}
            </div>`;

        wrap.querySelector('.ingredient-card-stock-toggle')?.addEventListener('click', () => {
            if (state.stockEditorOpen) {
                state.stockEditorOpen = false;
                state.stockDraftQty = null;
            } else {
                state.stockEditorOpen = true;
                state.shopEditorOpen = false;
                state.stockDraftQty = qty;
            }
            render();
        });

        wrap.querySelectorAll('.ingredient-card-stock-step').forEach((btn) => {
            btn.addEventListener('click', () => {
                const dir = Number(btn.dataset.dir) || 1;
                const next = normalizeQty((Number(state.stockDraftQty ?? qty) || 0) + step * dir);
                state.stockDraftQty = next;
                render();
            });
        });

        wrap.querySelector('.ingredient-card-stock-input')?.addEventListener('input', (event) => {
            state.stockDraftQty = usesPackStep
                ? normalizeQty((Number(event.target.value) || 0) * step)
                : normalizeQty(event.target.value);
        });

        wrap.querySelector('.ingredient-card-stock-clear')?.addEventListener('click', () => {
            state.stockDraftQty = 0;
            render();
        });

        wrap.querySelector('.ingredient-card-stock-save')?.addEventListener('click', () => {
            const input = wrap.querySelector('.ingredient-card-stock-input');
            const nextQty = usesPackStep
                ? normalizeQty((Number(input?.value) || 0) * step)
                : normalizeQty(input?.value ?? state.stockDraftQty ?? qty);
            if (product) {
                setPantryProductQty(def.id, product.id, nextQty);
            } else {
                setPantryQty(def.id, nextQty);
            }
            state.stockEditorOpen = false;
            state.stockDraftQty = null;
            render();
            state.onAfterChange?.();
        });
    }

    function renderShopEditorInto(wrap, def, product) {
        const { step, usesPackStep } = getQtyStepMeta(def, product);
        const packSize = product?.packSize || def.purchasePack?.amount;
        const packLabel = product?.packLabel || def.purchasePack?.label;
        const usesPacks = Boolean(packSize && packSize > 0);
        const defaultAmount = step;
        const shoppingItem = getShoppingItemFor(def, product);
        const hasShoppingItem = Boolean(shoppingItem);
        const shoppingAmount = shoppingItem?.amount || 0;
        const draftQty = state.shopEditorOpen
            ? normalizeQty(state.shopDraftQty ?? (shoppingAmount || defaultAmount))
            : shoppingAmount;
        const shopValueLabel = hasShoppingItem
            ? usesPackStep
                ? formatPackCount(shoppingAmount, step)
                : formatPackAwareAmount(shoppingAmount, def.pantryUnit, packSize, packLabel)
            : 'Brak na liście';
        const shopSubLabel = hasShoppingItem && usesPackStep
            ? formatQtyWithUnit(shoppingAmount, def.pantryUnit)
            : '';
        const shopInputValue = usesPackStep
            ? formatPreciseQty(draftQty / step)
            : formatPreciseQty(draftQty);
        const shopInputUnit = usesPackStep ? 'opak.' : unitLabel(def.pantryUnit);
        const actionLabel = state.shopEditorOpen ? 'Anuluj' : (hasShoppingItem ? 'Zmień' : 'Dodaj');

        wrap.innerHTML = `
            <p class="text-[9px] font-semibold uppercase tracking-wide mb-1.5" style="color:rgb(var(--text-dim-rgb));">Lista zakupów</p>
            <div class="rounded-2xl border px-3 py-3" style="background:rgb(var(--card-rgb)); border-color:rgb(var(--card-strong-rgb));">
                <div class="flex items-start justify-between gap-3">
                    <div class="min-w-0 flex-1">
                            <p class="text-[16px] font-bold tabular-nums" style="color:${hasShoppingItem ? 'rgb(var(--text-body-rgb))' : 'rgb(var(--text-dim-rgb))'};">${esc(shopValueLabel)}</p>
                            ${shopSubLabel ? `<p class="text-[11px] mt-1" style="color:rgb(var(--text-dim-rgb));">${esc(shopSubLabel)}</p>` : ''}
                    </div>
                    <button type="button" class="ingredient-card-shop-toggle inline-flex items-center rounded-full px-2.5 py-1 text-[10px] font-semibold shrink-0" style="background:${state.shopEditorOpen ? 'rgb(var(--sunken-rgb))' : 'rgb(var(--card-soft-rgb))'}; color:${state.shopEditorOpen ? 'rgb(var(--text-emphasis-rgb))' : 'rgb(var(--text-body-soft-rgb))'};">
                        ${esc(actionLabel)}
                    </button>
                </div>
                ${state.shopEditorOpen ? `
                    <div class="mt-3 pt-3 border-t" style="border-color:rgb(var(--card-strong-rgb));">
                        <div class="flex items-center gap-2">
                            <button type="button" class="ingredient-card-shop-step w-9 h-9 rounded-xl flex items-center justify-center shrink-0" style="background:rgb(var(--card-soft-rgb)); color:rgb(var(--text-body-soft-rgb));" data-dir="-1" aria-label="Zmniejsz ilość na liście">
                                <i class="fas fa-minus text-xs"></i>
                            </button>
                            <label class="flex-1 rounded-xl px-3 py-2 flex items-center justify-center gap-2" style="background:rgb(var(--card-soft-rgb));">
                                <input type="number" min="0" step="${usesPackStep ? '1' : defaultAmount}" value="${shopInputValue}" class="ingredient-card-shop-input w-20 bg-transparent text-center text-[14px] font-semibold tabular-nums outline-none appearance-none" style="color:rgb(var(--text-body-rgb)); background:transparent !important; border:none !important; box-shadow:none !important; -webkit-appearance:none; -moz-appearance:textfield;">
                                <span class="text-[12px] font-medium shrink-0" style="color:rgb(var(--text-dim-rgb));">${esc(shopInputUnit)}</span>
                            </label>
                            <button type="button" class="ingredient-card-shop-step w-9 h-9 rounded-xl flex items-center justify-center shrink-0" style="background:rgb(var(--card-soft-rgb)); color:rgb(var(--text-body-soft-rgb));" data-dir="1" aria-label="Zwiększ ilość na liście">
                                <i class="fas fa-plus text-xs"></i>
                            </button>
                        </div>
                        ${usesPackStep ? `<p class="text-[10px] mt-2 text-right" style="color:rgb(var(--text-dim-rgb));">${esc(formatQtyWithUnit(draftQty, def.pantryUnit))}</p>` : ''}
                        <div class="flex items-center justify-between gap-3 mt-3">
                            ${hasShoppingItem
                                ? '<button type="button" class="ingredient-card-shop-remove text-[11px] font-semibold" style="color:rgb(var(--text-dim-rgb));">Usuń z listy</button>'
                                : '<span></span>'}
                            <button type="button" class="ingredient-card-shop-save inline-flex items-center rounded-full px-3 py-1.5 text-[11px] font-semibold" style="background:rgb(var(--text-body-rgb)); color:rgb(var(--app-bg-rgb));">Zapisz</button>
                        </div>
                    </div>` : ''}
            </div>`;

        wrap.querySelector('.ingredient-card-shop-toggle')?.addEventListener('click', () => {
            if (state.shopEditorOpen) {
                state.shopEditorOpen = false;
                state.shopDraftQty = null;
            } else {
                state.shopEditorOpen = true;
                state.stockEditorOpen = false;
                state.shopDraftQty = shoppingAmount || defaultAmount;
            }
            render();
        });

        wrap.querySelectorAll('.ingredient-card-shop-step').forEach((btn) => {
            btn.addEventListener('click', () => {
                const dir = Number(btn.dataset.dir) || 1;
                const next = normalizeQty((Number(state.shopDraftQty ?? (shoppingAmount || defaultAmount)) || 0) + step * dir);
                state.shopDraftQty = next;
                render();
            });
        });

        wrap.querySelector('.ingredient-card-shop-input')?.addEventListener('input', (event) => {
            state.shopDraftQty = usesPackStep
                ? normalizeQty((Number(event.target.value) || 0) * step)
                : normalizeQty(event.target.value);
        });

        wrap.querySelector('.ingredient-card-shop-remove')?.addEventListener('click', () => {
            if (!shoppingItem) return;
            removeItemFromList(KITCHEN_LIST_ID, shoppingItem.id);
            state.shopEditorOpen = false;
            state.shopDraftQty = null;
            render();
            state.onAfterChange?.();
            window.refreshShopping?.();
            showAppToast(`Usunieto ${product?.name || def.name} z listy.`);
        });

        wrap.querySelector('.ingredient-card-shop-save')?.addEventListener('click', () => {
            const input = wrap.querySelector('.ingredient-card-shop-input');
            const nextAmount = usesPackStep
                ? normalizeQty((Number(input?.value) || 0) * step)
                : normalizeQty(input?.value ?? state.shopDraftQty ?? defaultAmount);
            let toastText = null;
            if (shoppingItem) {
                updateKitchenItemAmount(KITCHEN_LIST_ID, shoppingItem.id, nextAmount);
                toastText = nextAmount > 0
                    ? `Zaktualizowano ${product?.name || def.name}.`
                    : `Usunieto ${product?.name || def.name} z listy.`;
            } else if (nextAmount > 0) {
                const note = usesPacks ? (packLabel || `${formatQty(packSize)} ${unitLabel(def.pantryUnit)}`) : undefined;
                const line = {
                    ingredientId: def.id,
                    amount: nextAmount,
                    unit: unitLabel(def.pantryUnit),
                    name: product?.name || def.name,
                    category: def.category,
                    sourceNote: note || state.sourceNote || defaultSourceNote,
                };
                if (product) line.productId = product.id;
                addOrMergeShoppingLines([line]);
                toastText = `Dodano ${product?.name || def.name}.`;
            }
            state.shopEditorOpen = false;
            state.shopDraftQty = null;
            render();
            state.onAfterChange?.();
            window.refreshShopping?.();
            if (toastText) showAppToast(toastText);
        });
    }

    function renderStock() {
        const wrap = el('stock');
        if (!wrap || !state.ingredientId) return;
        const def = INGREDIENTS[state.ingredientId];
        if (!def) return;

        const hasProducts = ingredientHasProducts(state.ingredientId);
        const isListMode = hasProducts && state.allowProductSelection && !state.productId;
        if (isListMode) {
            wrap.innerHTML = '';
            return;
        }

        const pantry = loadPantry();
        const product = state.productId ? PRODUCTS[state.productId] : null;
        renderStockEditorInto(wrap, def, product, pantry);
    }

    function productRowHtml(ingredientId, productId, pantry, kitchenItems) {
        const def = INGREDIENTS[ingredientId];
        const product = PRODUCTS[productId];
        const icon = CATEGORY_ICONS[def.category] || 'fa-jar';
        const unit = unitLabel(def.pantryUnit);
        const pantryQty = getPantryProducts(ingredientId, pantry).find((i) => i.productId === productId)?.qty || 0;
        const shoppingItem = kitchenItems.find((item) => !item.checked
            && item.ingredientId === ingredientId
            && item.unit === unit
            && (item.productId || '') === productId);
        const shoppingAmount = shoppingItem?.amount || 0;

        const pantryLabel = pantryQty > 0 ? `${formatQty(pantryQty)} ${unit}` : '—';
        const pantryColor = pantryQty > 0 ? 'rgb(var(--text-body-rgb))' : 'rgb(var(--text-subdued-rgb))';
        const shoppingLabel = shoppingAmount > 0 ? `${formatQty(shoppingAmount)} ${unit}` : '';

        return `<button type="button" class="ingredient-card-product-row w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-left transition-colors active:scale-[0.99]" style="background:rgb(var(--card-rgb)); border:1px solid transparent;" data-product-id="${esc(productId)}">
            ${mediaHtml(product.image || def.image, icon)}
            <div class="flex-1 min-w-0">
                <span class="text-[13px] font-semibold truncate block" style="color:rgb(var(--text-body-rgb));">${esc(product.name)}</span>
                ${product.packLabel ? `<span class="text-[10px] block mt-0.5" style="color:rgb(var(--text-dim-rgb));">${esc(product.packLabel)}</span>` : ''}
            </div>
            <div class="flex flex-col items-end gap-0.5 shrink-0 tabular-nums">
                <span class="text-[11px] font-semibold inline-flex items-center gap-1" style="color:${pantryColor};">
                    <i class="fas fa-box text-[9px]" style="color:rgb(var(--text-faint-rgb));"></i>${esc(pantryLabel)}
                </span>
                ${shoppingLabel ? `<span class="text-[11px] font-semibold inline-flex items-center gap-1" style="color:rgb(var(--text-body-rgb));">
                    <i class="fas fa-cart-shopping text-[9px]" style="color:rgb(var(--text-faint-rgb));"></i>${esc(shoppingLabel)}
                </span>` : ''}
            </div>
            <i class="fas fa-chevron-right text-[10px] shrink-0" style="color:rgb(var(--text-faint-rgb));"></i>
        </button>`;
    }

    function renderProducts() {
        const wrap = el('products');
        if (!wrap || !state.ingredientId) return;
        const hasProducts = ingredientHasProducts(state.ingredientId);
        const isListMode = hasProducts && state.allowProductSelection && !state.productId;
        if (!isListMode) {
            wrap.innerHTML = '';
            return;
        }

        const pantry = loadPantry();
        const products = sortProductsByStock(getProductsForIngredient(state.ingredientId), getPantryProducts(state.ingredientId, pantry));
        const shopping = loadShoppingState();
        const kitchen = shopping.lists.find((list) => list.id === KITCHEN_LIST_ID && list.type === 'kitchen');
        const kitchenItems = (kitchen && kitchen.type === 'kitchen') ? kitchen.items : [];

        wrap.innerHTML = `
            <p class="text-[9px] font-semibold uppercase tracking-wide mb-1.5" style="color:rgb(var(--text-dim-rgb));">Produkty</p>
            <div class="space-y-1.5">
                ${products.map((product) => productRowHtml(state.ingredientId, product.id, pantry, kitchenItems)).join('')}
            </div>`;

        wrap.querySelectorAll('.ingredient-card-product-row').forEach((btn) => {
            btn.addEventListener('click', () => {
                const nextProductId = btn.dataset.productId || null;
                if (!nextProductId) return;
                state.productId = nextProductId;
                state.selectedProductId = nextProductId;
                resetInlineEditors();
                state.onProductChange?.(nextProductId);
                render();
                state.onAfterChange?.();
            });
        });
    }

    function renderShop() {
        const wrap = el('shop');
        if (!wrap || !state.ingredientId) return;
        const def = INGREDIENTS[state.ingredientId];
        if (!def) return;

        const hasProducts = ingredientHasProducts(state.ingredientId);
        const isListMode = hasProducts && state.allowProductSelection && !state.productId;
        if (isListMode) {
            wrap.innerHTML = '';
            return;
        }

        const product = state.productId ? PRODUCTS[state.productId] : null;
        renderShopEditorInto(wrap, def, product);
    }

    function render() {
        if (!state.ingredientId) return;
        const def = INGREDIENTS[state.ingredientId];
        if (!def) return;
        const product = state.productId ? PRODUCTS[state.productId] : null;
        const pantry = loadPantry();

        renderHeader(def, product, pantry);
        renderNutrition(def, product);
        renderStock();
        renderProducts();
        renderShop();
    }

    function open({
        ingredientId,
        productId = null,
        selectedProductId = productId,
        allowProductSelection = true,
        sourceNote = defaultSourceNote,
        onProductChange = null,
        onAfterChange = null,
    } = {}) {
        const def = ingredientId ? INGREDIENTS[ingredientId] : null;
        const overlay = el('overlay');
        const card = el();
        if (!def || !overlay || !card) return;

        state.ingredientId = ingredientId;
        state.productId = productId && PRODUCTS[productId] ? productId : null;
        state.selectedProductId = selectedProductId && PRODUCTS[selectedProductId] ? selectedProductId : state.productId;
        state.allowProductSelection = Boolean(allowProductSelection);
        state.sourceNote = sourceNote;
        state.onProductChange = onProductChange;
        state.onAfterChange = onAfterChange;
        resetInlineEditors();
        render();

        clearTimeout(state.closeTimer);
        overlay.classList.remove('hidden');
        overlay.style.pointerEvents = 'auto';
        requestAnimationFrame(() => {
            overlay.classList.add('opacity-100');
            card.style.opacity = '1';
            card.style.transform = 'translateY(0)';
        });
    }

    function close() {
        const overlay = el('overlay');
        const card = el();
        if (overlay && card) {
            overlay.classList.remove('opacity-100');
            overlay.style.pointerEvents = 'none';
            card.style.opacity = '0';
            card.style.transform = 'translateY(1.5rem)';
            state.closeTimer = setTimeout(() => overlay.classList.add('hidden'), 220);
        }
        state.ingredientId = null;
        state.productId = null;
        state.selectedProductId = null;
        state.allowProductSelection = true;
        state.onProductChange = null;
        state.onAfterChange = null;
        state.sourceNote = defaultSourceNote;
        resetInlineEditors();
    }

    function bind() {
        if (bound) return;
        bound = true;
        el('close')?.addEventListener('click', close);
        el('back')?.addEventListener('click', () => {
            if (!state.allowProductSelection) return;
            state.productId = null;
            resetInlineEditors();
            render();
        });
        el('overlay')?.addEventListener('click', (event) => {
            if (event.target.id === `${idBase}-overlay`) close();
        });
    }

    function refresh() {
        if (state.ingredientId) render();
    }

    return {
        bind,
        open,
        close,
        refresh,
        isOpen: () => Boolean(state.ingredientId),
    };
}
