import { INGREDIENTS } from '../data/catalog.js?v=9';
import {
    loadPantry,
    computeShortfalls,
    categoryLabel,
    addAmountToPantry,
    subtractFromPantry,
    getSelectedDays,
    setSelectedDays,
    addSessionLogEntry,
    removeSessionLogEntry,
    clearSessionLog,
    loadShoppingSession,
} from '../services/pantryShopping.js?v=2';
import { aggregateSelectedDaysIngredientNeed } from '../services/planIngredients.js?v=2';
import { loadPlans, dateKey } from '../services/planStore.js?v=2';
import { addDays, startOfDay, startOfMonth } from '../services/dateUtils.js';
import { createSwipePopoverCalendarHTML, initSwipePopoverCalendar } from '../ui/swipePopoverCalendar.js';
import { showAppToast } from '../ui/toast.js';

/* ── helpers ── */

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

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

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

function stepForUnit(unit) {
    return (unit === 'szt.' || unit === 'szt') ? 1 : 10;
}


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',
};
const CATEGORY_ORDER = ['pieczywo', 'nabial', 'mieso_ryby', 'warzywa', 'owoce', 'suche', 'przyprawy', 'inne'];
const DAY_ABBR = ['Nd', 'Pn', 'Wt', 'Śr', 'Cz', 'Pt', 'Sb'];
const DAY_NAMES_SHORT = ['nd.', 'pon.', 'wt.', 'śr.', 'czw.', 'pt.', 'sob.'];
const WEEKDAY_SHORT = ['pn', 'wt', 'śr', 'cz', 'pt', 'sb', 'nd'];
const MONTHS_LONG = ['Styczeń','Luty','Marzec','Kwiecień','Maj','Czerwiec','Lipiec','Sierpień','Wrzesień','Październik','Listopad','Grudzień'];
const MONTHS_SHORT = ['sty','lut','mar','kwi','maj','cze','lip','sie','wrz','paź','lis','gru'];
const CALENDAR_DIM_TEXT = 'rgb(var(--text-faint-rgb))';
const CALENDAR_DIM_OPACITY = '0.58';

/* ── module state ── */
let boughtPopupOpen = false;
let expandedIngredientId = null;
let expandedAmount = 0;
let calendarOpen = false;
let calendarMonth = startOfMonth(new Date());
let shoppingCalendar = null;

/* ── day helpers ── */

function todayKey() { return dateKey(new Date()); }

function getDefaultSelectedDays() {
    const today = startOfDay(new Date());
    return Array.from({ length: 7 }, (_, i) => dateKey(addDays(today, i)));
}

function formatRangePill(selectedDays) {
    if (selectedDays.length === 0) return 'Wybierz dni';
    const sorted = [...selectedDays].sort();
    const fmt = (dk) => {
        const d = new Date(dk + 'T00:00:00');
        return `${DAY_NAMES_SHORT[d.getDay()]} ${d.getDate()} ${MONTHS_SHORT[d.getMonth()]}`;
    };
    if (sorted.length === 1) return fmt(sorted[0]);
    return `${fmt(sorted[0])} – ${fmt(sorted[sorted.length - 1])}`;
}

/* ── computed data ── */

function computeActiveItems() {
    const selectedDays = getSelectedDays();
    if (selectedDays.length === 0) return [];
    const plans = loadPlans();
    const needLines = aggregateSelectedDaysIngredientNeed(plans, selectedDays);
    return computeShortfalls(needLines, loadPantry());
}

function getSessionLog() {
    return loadShoppingSession().sessionLog;
}

function groupByCategory(items) {
    const groups = new Map();
    for (const item of items) {
        const cat = item.category || 'inne';
        if (!groups.has(cat)) groups.set(cat, []);
        groups.get(cat).push(item);
    }
    return CATEGORY_ORDER
        .filter((cat) => groups.has(cat))
        .map((cat) => ({ cat, items: groups.get(cat) }));
}

/* ══════════════════════ HTML SHELL ══════════════════════ */

export function getShoppingListHTML() {
    return `
    <div id="shopping-view" class="hidden flex flex-col h-full absolute inset-0 overflow-hidden z-10" style="background:rgb(var(--app-bg-rgb)) !important;">

        <!-- ── header ── -->
        <div class="shrink-0 px-4 pt-5 pb-0">

            <!-- title row + pill + bought button (position:relative anchors the popups) -->
            <div class="flex items-center gap-2 mb-4" style="position:relative;">
                <h1 class="flex-1 text-[18px] font-bold" style="color:rgb(var(--text-emphasis-rgb));">Lista zakupów</h1>
                <button type="button" id="sl-range-pill" class="min-w-0 max-w-[10rem] h-10 rounded-full flex items-center gap-1.5 px-2.5 transition-all shrink" style="background:rgb(var(--card-rgb)); border:1px solid rgb(var(--border-card-rgb)); box-shadow:var(--shadow-shell);">
                    <span id="sl-range-label" class="min-w-0 flex-1 text-left text-[13px] font-normal truncate" style="color:rgb(var(--text-body-rgb));"></span>
                    <i id="sl-range-chevron" class="fas fa-chevron-down text-[10px] shrink-0 transition-transform duration-200" style="color:rgb(var(--text-dim-rgb));"></i>
                </button>
                <button type="button" id="sl-bought-btn" class="relative h-10 w-10 rounded-full flex items-center justify-center transition-all shrink-0" style="background:rgb(var(--card-rgb)); border:1px solid rgb(var(--border-card-rgb)); box-shadow:var(--shadow-shell);" aria-label="Kupione">
                    <i class="fas fa-check text-[13px]" style="color:rgb(var(--text-body-rgb));"></i>
                    <span id="sl-bought-badge" class="absolute -top-0.5 -right-0.5 min-w-[16px] h-4 px-1 rounded-full text-[9px] font-bold items-center justify-center" style="background:rgb(var(--success-rgb)); color:rgb(var(--on-accent-rgb)); display:none;">0</span>
                </button>

                <!-- popup calendar (absolute, overlays content below) -->
                <div id="sl-calendar-popup" style="position:absolute; top:calc(100% + 0.5rem); left:0; right:0; z-index:50; pointer-events:none; opacity:0; transform:translateY(-6px) scale(0.98); transition: opacity 0.2s ease, transform 0.2s ease;">
                    <div class="rounded-[1.35rem] py-3" style="background:rgb(var(--sunken-rgb)); border:1px solid rgb(var(--border-input-rgb)); box-shadow:var(--shadow-shell);">
                        ${createSwipePopoverCalendarHTML({
        idPrefix: 'sl-cal',
        weekdays: WEEKDAY_SHORT,
    })}
                    </div>
                </div>

                <!-- popup bought (absolute, overlays content below) -->
                <div id="sl-bought-popup" style="position:absolute; top:calc(100% + 0.5rem); left:0; right:0; z-index:50; pointer-events:none; opacity:0; transform:translateY(-6px) scale(0.98); transition: opacity 0.2s ease, transform 0.2s ease;">
                    <div class="rounded-[1.35rem] px-3 py-3" style="background:rgb(var(--sunken-rgb)); border:1px solid rgb(var(--border-input-rgb)); box-shadow:var(--shadow-shell);">
                        <div class="flex items-center justify-between mb-2 px-1">
                            <span class="text-[11px] font-bold uppercase tracking-wider" style="color:rgb(var(--text-dim-rgb));">Kupione (<span id="sl-bought-popup-count">0</span>)</span>
                            <button type="button" id="sl-clear-session" class="h-8 px-2 rounded-full text-[11px] font-semibold transition-colors" style="background:transparent; border:none; color:rgb(var(--text-muted-rgb));">
                                Wyczyść
                            </button>
                        </div>
                        <div id="sl-bought-list" class="max-h-[50vh] overflow-y-auto no-scrollbar"></div>
                    </div>
                </div>
            </div>
        </div>

        <!-- ── scrollable list ── -->
        <div id="sl-scroll" class="flex-1 overflow-y-auto no-scrollbar px-4 pt-3 pb-24" style="background:rgb(var(--app-bg-rgb)) !important;">
            <div id="sl-board"></div>
        </div>
    </div>`;
}

/* ══════════════════════ CALENDAR ══════════════════════ */

function initShoppingCalendar() {
    shoppingCalendar = initSwipePopoverCalendar({
        idPrefix: 'sl-cal',
        selectionMode: 'range',
        monthsLong: MONTHS_LONG,
        getMonthAnchor: () => calendarMonth,
        setMonthAnchor: (nextMonth) => {
            calendarMonth = startOfMonth(nextMonth);
        },
        getSelectionKeys: () => getSelectedDays(),
        onSelectionCommit: (rangeKeys) => {
            setSelectedDays(rangeKeys);
            expandedIngredientId = null;
            updatePillLabel();
            renderAll();
        },
        resolveDayState: (day, { inCurrentMonth, isSelected }) => ({
            disabled: false,
            dimmed: !inCurrentMonth,
            showDot: dateKey(day) === todayKey() && !isSelected,
        }),
        theme: {
            selectedBg: 'rgb(var(--card-rgb))',
            selectedBorder: 'rgb(var(--border-input-rgb))',
            selectedText: 'rgb(var(--text-emphasis-rgb))',
            selectedDot: 'rgb(var(--text-emphasis-rgb))',
            bg: 'rgb(var(--app-bg-rgb))',
            border: 'rgb(var(--card-raised-rgb))',
            text: 'rgb(var(--text-body-soft-rgb))',
            dimmedBg: 'transparent',
            dimText: CALENDAR_DIM_TEXT,
            dimOpacity: Number(CALENDAR_DIM_OPACITY),
            dot: 'rgb(var(--text-faint-rgb))',
        },
    });
}

function updatePillLabel() {
    const el = document.getElementById('sl-range-label');
    if (el) el.textContent = formatRangePill(getSelectedDays());
}

function openCalendar() {
    if (boughtPopupOpen) closeBoughtPopup();
    calendarOpen = true;
    calendarMonth = startOfMonth(new Date());
    const popup = document.getElementById('sl-calendar-popup');
    const viewport = document.getElementById('sl-cal-viewport');
    const chevron = document.getElementById('sl-range-chevron');
    const pill = document.getElementById('sl-range-pill');
    if (popup) {
        popup.style.pointerEvents = 'auto';
        popup.style.opacity = '1';
        popup.style.transform = 'translateY(0) scale(1)';
    }
    if (chevron) chevron.style.transform = 'rotate(180deg)';
    if (pill) {
        pill.style.background = 'rgb(var(--sunken-rgb))';
        pill.style.borderColor = 'rgb(var(--border-input-rgb))';
    }
    if (viewport) {
        viewport.style.opacity = '0';
        viewport.style.visibility = 'hidden';
        viewport.style.transition = 'opacity 120ms ease';
    }
    shoppingCalendar?.render();
    // Compute geometry while hidden; reveal only after stable layout.
    const ensureStableCalendarLayout = (attempt = 0) => {
        const vw = viewport ? (viewport.clientWidth || viewport.getBoundingClientRect().width) : 0;
        if (vw < 8 && attempt < 8) {
            requestAnimationFrame(() => ensureStableCalendarLayout(attempt + 1));
            return;
        }
        shoppingCalendar?.reapplyLayout();
        shoppingCalendar?.resetTrackPosition();
        requestAnimationFrame(() => {
            shoppingCalendar?.reapplyLayout();
            shoppingCalendar?.resetTrackPosition();
            if (viewport) {
                viewport.style.visibility = 'visible';
                viewport.style.opacity = '1';
            }
        });
    };
    requestAnimationFrame(() => ensureStableCalendarLayout());
}

function closeCalendar() {
    calendarOpen = false;
    const popup = document.getElementById('sl-calendar-popup');
    const chevron = document.getElementById('sl-range-chevron');
    const pill = document.getElementById('sl-range-pill');
    if (popup) {
        popup.style.pointerEvents = 'none';
        popup.style.opacity = '0';
        popup.style.transform = 'translateY(-6px) scale(0.98)';
    }
    if (chevron) chevron.style.transform = '';
    if (pill) {
        pill.style.background = 'rgb(var(--card-rgb))';
        pill.style.borderColor = 'rgb(var(--border-card-rgb))';
    }
    shoppingCalendar?.resetTrackPosition();
}

function toggleCalendar() {
    calendarOpen ? closeCalendar() : openCalendar();
}

function openBoughtPopup() {
    if (calendarOpen) closeCalendar();
    boughtPopupOpen = true;
    const popup = document.getElementById('sl-bought-popup');
    const btn = document.getElementById('sl-bought-btn');
    if (popup) {
        popup.style.pointerEvents = 'auto';
        popup.style.opacity = '1';
        popup.style.transform = 'translateY(0) scale(1)';
    }
    if (btn) {
        btn.style.background = 'rgb(var(--sunken-rgb))';
        btn.style.borderColor = 'rgb(var(--border-input-rgb))';
    }
}

function closeBoughtPopup() {
    boughtPopupOpen = false;
    const popup = document.getElementById('sl-bought-popup');
    const btn = document.getElementById('sl-bought-btn');
    if (popup) {
        popup.style.pointerEvents = 'none';
        popup.style.opacity = '0';
        popup.style.transform = 'translateY(-6px) scale(0.98)';
    }
    if (btn) {
        btn.style.background = 'rgb(var(--card-rgb))';
        btn.style.borderColor = 'rgb(var(--border-card-rgb))';
    }
}

function toggleBoughtPopup() {
    boughtPopupOpen ? closeBoughtPopup() : openBoughtPopup();
}

/* ══════════════════════ ITEM ROWS ══════════════════════ */

function activeItemHtml(item) {
    const def = INGREDIENTS[item.ingredientId];
    const icon = CATEGORY_ICONS[def?.category] || 'fa-jar';
    const image = def?.image;
    const mediaFit = image?.endsWith('.svg') ? 'object-contain' : 'object-cover';
    const mediaHtml = image
        ? `<img src="${esc(image)}" alt="" class="w-8 h-8 rounded-lg ${mediaFit} shrink-0">`
        : `<div class="w-8 h-8 rounded-lg flex items-center justify-center shrink-0" style="background:rgb(var(--card-soft-rgb));"><i class="fas ${icon} text-xs" style="color:rgb(var(--text-faint-rgb));"></i></div>`;

    const isExpanded = expandedIngredientId === item.ingredientId;
    const step = stepForUnit(item.unit);
    const stepAmt = isExpanded ? expandedAmount : Math.max(step, Math.round(item.shortfall / step) * step);

    return `
    <div class="sl-swipe-wrap relative rounded-xl mb-1.5 overflow-hidden" style="background:rgb(var(--success-rgb)); box-shadow:var(--shadow-card);">
        <div class="sl-swipe-bg-buy absolute inset-0 flex items-center pr-5 justify-end">
            <i class="fas fa-check text-white text-lg"></i>
        </div>
        <div class="sl-swipe-inner rounded-xl" style="background:rgb(var(--card-rgb)); position:relative;"
            data-id="${esc(item.ingredientId)}" data-unit="${esc(item.unit)}" data-shortfall="${item.shortfall}">
            <div class="sl-item-main flex items-center gap-3 py-1.5 px-3 cursor-pointer select-none">
                ${mediaHtml}
                <div class="flex-1 min-w-0">
                    <span class="block text-[13px] font-medium leading-tight truncate" style="color:rgb(var(--text-body-rgb));">${esc(item.name)}</span>
                </div>
                <span class="text-[13px] font-semibold tabular-nums shrink-0" style="color:rgb(var(--text-muted-rgb));">${esc(formatQty(item.shortfall))} ${esc(unitLabel(item.unit))}</span>
                <i class="fas fa-chevron-${isExpanded ? 'up' : 'down'} text-[9px] shrink-0" style="color:rgb(var(--text-subdued-rgb));"></i>
            </div>
            <div class="sl-step-row overflow-hidden" data-step-for="${esc(item.ingredientId)}"
                style="max-height:${isExpanded ? '60px' : '0'}; transition: max-height 0.2s ease;">
                <div class="flex items-center gap-2 px-3 pb-3">
                    <button type="button" class="sl-step-minus w-9 h-9 rounded-xl flex items-center justify-center shrink-0 active:scale-95" style="background:rgb(var(--card-soft-rgb)); color:rgb(var(--text-body-soft-rgb));" aria-label="Zmniejsz ilość">
                        <i class="fas fa-minus text-xs"></i>
                    </button>
                    <div class="flex-1 rounded-xl px-3 py-2 flex items-center justify-center gap-2" style="background:rgb(var(--card-soft-rgb));">
                        <span class="sl-exp-amount text-[14px] font-semibold tabular-nums" style="color:rgb(var(--text-body-rgb));">${esc(formatQty(stepAmt))}</span>
                        <span class="text-[12px] font-medium shrink-0" style="color:rgb(var(--text-dim-rgb));">${esc(unitLabel(item.unit))}</span>
                    </div>
                    <button type="button" class="sl-step-plus w-9 h-9 rounded-xl flex items-center justify-center shrink-0 active:scale-95" style="background:rgb(var(--card-soft-rgb)); color:rgb(var(--text-body-soft-rgb));" aria-label="Zwiększ ilość">
                        <i class="fas fa-plus text-xs"></i>
                    </button>
                    <button type="button" class="sl-step-confirm px-3 py-1.5 rounded-lg text-[12px] font-semibold shrink-0 active:scale-95" style="background:rgb(var(--text-body-rgb)); color:rgb(var(--app-bg-rgb));">Dodaj</button>
                </div>
            </div>
        </div>
    </div>`;
}

function boughtItemHtml(entry) {
    const def = INGREDIENTS[entry.ingredientId];
    const icon = CATEGORY_ICONS[def?.category] || 'fa-jar';
    const image = def?.image;
    const mediaFit = image?.endsWith('.svg') ? 'object-contain' : 'object-cover';
    const mediaHtml = image
        ? `<img src="${esc(image)}" alt="" class="w-8 h-8 rounded-lg ${mediaFit} shrink-0">`
        : `<div class="w-8 h-8 rounded-lg flex items-center justify-center shrink-0" style="background:rgb(var(--card-soft-rgb));"><i class="fas ${icon} text-xs" style="color:rgb(var(--text-faint-rgb));"></i></div>`;

    return `
    <div class="sl-swipe-wrap relative rounded-xl mb-1.5 overflow-hidden" style="background:rgb(var(--danger-rgb)); box-shadow:var(--shadow-card);">
        <div class="sl-swipe-bg-undo absolute inset-0 flex items-center pl-5 justify-start">
            <i class="fas fa-rotate-left text-white text-lg"></i>
        </div>
        <div class="sl-swipe-inner flex items-center gap-3 py-1.5 px-3 rounded-xl" style="background:rgb(var(--card-rgb)); position:relative;" data-entry-id="${esc(entry.id)}">
            ${mediaHtml}
            <div class="flex-1 min-w-0">
                <span class="block text-[13px] font-medium leading-tight truncate" style="color:rgb(var(--text-body-rgb));">${esc(entry.name)}</span>
            </div>
            <span class="text-[13px] font-semibold tabular-nums shrink-0" style="color:rgb(var(--text-muted-rgb));">${esc(formatQty(entry.addedAmount))} ${esc(unitLabel(entry.unit))}</span>
        </div>
    </div>`;
}

/* ══════════════════════ SWIPE ══════════════════════ */

function attachSwipe(container, opts) {
    const inner = container.querySelector('.sl-swipe-inner');
    if (!inner) return;
    let startX = 0, startY = 0, dx = 0, tracking = false, decided = false, goingH = false;

    container.addEventListener('pointerdown', (e) => {
        startX = e.clientX; startY = e.clientY; dx = 0;
        tracking = true; decided = false; goingH = false;
        inner.style.transition = 'none';
        container.setPointerCapture(e.pointerId);
    });

    container.addEventListener('pointermove', (e) => {
        if (!tracking) return;
        const ddx = e.clientX - startX;
        const ddy = e.clientY - startY;
        if (!decided) {
            if (Math.abs(ddx) < 6 && Math.abs(ddy) < 6) return;
            decided = true;
            goingH = Math.abs(ddx) > Math.abs(ddy);
        }
        if (!goingH) { tracking = false; inner.style.transform = ''; return; }
        dx = ddx;
        if (dx > 0 && opts.onRight) inner.style.transform = `translateX(${Math.min(dx, 90)}px)`;
        else if (dx < 0 && opts.onLeft) inner.style.transform = `translateX(${Math.max(dx, -90)}px)`;
    });

    const finish = () => {
        if (!tracking) return;
        tracking = false;
        inner.style.transition = 'transform 0.2s ease';
        const thr = 65;
        if (dx > thr && opts.onRight) {
            inner.style.transform = 'translateX(120%)';
            setTimeout(opts.onRight, 180);
        } else if (dx < -thr && opts.onLeft) {
            inner.style.transform = 'translateX(-120%)';
            setTimeout(opts.onLeft, 180);
        } else {
            inner.style.transform = '';
        }
        dx = 0;
    };

    container.addEventListener('pointerup', finish);
    container.addEventListener('pointercancel', () => {
        tracking = false;
        inner.style.transition = 'transform 0.2s ease';
        inner.style.transform = '';
        dx = 0;
    });
}

/* ══════════════════════ EXPAND / STEPPER ══════════════════════ */

function toggleExpand(ingredientId, unit, shortfall) {
    if (expandedIngredientId === ingredientId) {
        expandedIngredientId = null;
    } else {
        expandedIngredientId = ingredientId;
        const step = stepForUnit(unit);
        expandedAmount = Math.max(step, Math.round(shortfall / step) * step);
    }
    renderBoard();
}

function updateExpandedAmountDisplay() {
    const el = document.querySelector(`[data-step-for="${expandedIngredientId}"] .sl-exp-amount`);
    if (el) el.textContent = formatQty(expandedAmount);
}

/* ══════════════════════ BUY / UNDO ══════════════════════ */

function buyItem(ingredientId, unit, amount) {
    const def = INGREDIENTS[ingredientId];
    if (!def) return;
    addAmountToPantry(ingredientId, undefined, amount);
    addSessionLogEntry({ ingredientId, name: def.name, addedAmount: amount, unit, category: def.category || 'inne' });
    expandedIngredientId = null;
    renderAll();
    if (typeof window.refreshPantry === 'function') window.refreshPantry();
}

function undoBoughtEntry(entryId) {
    const log = getSessionLog();
    const entry = log.find((e) => e.id === entryId);
    if (!entry) return;
    subtractFromPantry(entry.ingredientId, entry.productId, entry.addedAmount);
    removeSessionLogEntry(entryId);
    renderAll();
    if (typeof window.refreshPantry === 'function') window.refreshPantry();
    showAppToast('Cofnięto zakup');
}

/* ══════════════════════ RENDERING ══════════════════════ */

function renderBoughtBadge(count) {
    const badge = document.getElementById('sl-bought-badge');
    if (!badge) return;
    if (count > 0) {
        badge.textContent = String(count);
        badge.style.display = 'flex';
    } else {
        badge.style.display = 'none';
    }
}

function renderBoard() {
    const root = document.getElementById('sl-board');
    if (!root) return;

    const selectedDays = getSelectedDays();
    if (selectedDays.length === 0) {
        root.innerHTML = `
        <div class="flex flex-col items-center justify-center py-16 text-center">
            <div class="w-14 h-14 rounded-2xl flex items-center justify-center mb-4" style="background:rgb(var(--card-rgb));">
                <i class="fas fa-calendar-days text-xl" style="color:rgb(var(--text-subdued-rgb));"></i>
            </div>
            <p class="text-[14px] font-semibold mb-1" style="color:rgb(var(--text-body-rgb));">Wybierz dni</p>
            <p class="text-[12px] max-w-[14rem]" style="color:rgb(var(--text-dim-rgb));">Otwórz kalendarz i zaznacz dni, na które chcesz zrobić zakupy.</p>
        </div>`;
        return;
    }

    const activeItems = computeActiveItems();

    if (activeItems.length === 0) {
        root.innerHTML = `
        <div class="flex flex-col items-center justify-center py-16 text-center">
            <div class="w-14 h-14 rounded-2xl flex items-center justify-center mb-4" style="background:rgb(var(--card-rgb));">
                <i class="fas fa-check text-xl" style="color:rgb(var(--success-rgb));"></i>
            </div>
            <p class="text-[14px] font-semibold mb-1" style="color:rgb(var(--text-body-rgb));">Wszystko masz</p>
            <p class="text-[12px] max-w-[14rem]" style="color:rgb(var(--text-dim-rgb));">Spiżarnia pokrywa zapotrzebowanie na wybrane dni.</p>
        </div>`;
        return;
    }

    const groups = groupByCategory(activeItems);
    root.innerHTML = groups.map(({ cat, items: catItems }) => {
        const icon = CATEGORY_ICONS[cat] || 'fa-jar';
        return `
        <section class="mb-4">
            <div class="flex items-center gap-1.5 mb-2 px-1">
                <i class="fas ${icon} text-[10px]" style="color:rgb(var(--text-dim-rgb));"></i>
                <p class="text-[10px] font-bold uppercase tracking-wider" style="color:rgb(var(--text-dim-rgb));">${esc(categoryLabel(cat))}</p>
                <span class="text-[10px]" style="color:rgb(var(--text-subdued-rgb));">${catItems.length}</span>
            </div>
            ${catItems.map((item) => activeItemHtml(item)).join('')}
        </section>`;
    }).join('');

    bindActiveRowEvents(root, activeItems);
}

function renderBought() {
    const sessionLog = getSessionLog();
    const countEl = document.getElementById('sl-bought-popup-count');
    const list = document.getElementById('sl-bought-list');
    if (!list || !countEl) return;

    countEl.textContent = String(sessionLog.length);

    if (sessionLog.length === 0) {
        list.innerHTML = `<div class="py-6 text-center text-[12px]" style="color:rgb(var(--text-dim-rgb));">Brak kupionych</div>`;
        return;
    }

    list.innerHTML = [...sessionLog].reverse().map((e) => boughtItemHtml(e)).join('');
    list.querySelectorAll('.sl-swipe-wrap').forEach((wrap) => {
        const entryId = wrap.querySelector('.sl-swipe-inner')?.dataset.entryId;
        if (!entryId) return;
        attachSwipe(wrap, { onLeft: () => undoBoughtEntry(entryId) });
    });
}

function renderAll() {
    const sessionLog = getSessionLog();
    renderBoughtBadge(sessionLog.length);
    renderBoard();
    renderBought();
}

/* ══════════════════════ ACTIVE ROW EVENTS ══════════════════════ */

function bindActiveRowEvents(root, activeItems) {
    root.querySelectorAll('.sl-swipe-wrap').forEach((wrap) => {
        const inner = wrap.querySelector('.sl-swipe-inner');
        const ingredientId = inner?.dataset.id;
        const unit = inner?.dataset.unit;
        const shortfall = parseFloat(inner?.dataset.shortfall || '0');
        if (!ingredientId) return;

        const item = activeItems.find((i) => i.ingredientId === ingredientId);
        attachSwipe(wrap, { onRight: item ? () => buyItem(item.ingredientId, item.unit, item.shortfall) : undefined });

        inner.querySelector('.sl-item-main')?.addEventListener('click', () => toggleExpand(ingredientId, unit, shortfall));

        inner.querySelector('.sl-step-minus')?.addEventListener('click', (e) => {
            e.stopPropagation();
            const step = stepForUnit(unit);
            expandedAmount = Math.max(step, expandedAmount - step);
            updateExpandedAmountDisplay();
        });

        inner.querySelector('.sl-step-plus')?.addEventListener('click', (e) => {
            e.stopPropagation();
            expandedAmount += stepForUnit(unit);
            updateExpandedAmountDisplay();
        });

        inner.querySelector('.sl-step-confirm')?.addEventListener('click', (e) => {
            e.stopPropagation();
            if (expandedAmount > 0) buyItem(ingredientId, unit, expandedAmount);
        });
    });
}

/* ══════════════════════ PUBLIC API ══════════════════════ */

export function refreshShoppingList() {
    updatePillLabel();
    renderAll();
    if (calendarOpen) shoppingCalendar?.render();
}

export function setupShoppingList() {
    if (getSelectedDays().length === 0) setSelectedDays(getDefaultSelectedDays());

    updatePillLabel();
    renderAll();

    initShoppingCalendar();

    document.getElementById('sl-range-pill')?.addEventListener('click', (e) => {
        e.stopPropagation();
        toggleCalendar();
    });

    document.getElementById('sl-bought-btn')?.addEventListener('click', (e) => {
        e.stopPropagation();
        toggleBoughtPopup();
    });

    document.addEventListener('click', (e) => {
        if (calendarOpen) {
            const popup = document.getElementById('sl-calendar-popup');
            const pill = document.getElementById('sl-range-pill');
            if (popup && !popup.contains(e.target) && pill && !pill.contains(e.target)) {
                closeCalendar();
            }
        }
        if (boughtPopupOpen) {
            const popup = document.getElementById('sl-bought-popup');
            const btn = document.getElementById('sl-bought-btn');
            if (popup && !popup.contains(e.target) && btn && !btn.contains(e.target)) {
                closeBoughtPopup();
            }
        }
    });

    document.getElementById('sl-clear-session')?.addEventListener('click', (e) => {
        e.stopPropagation();
        clearSessionLog();
        renderAll();
    });

    window.refreshShoppingList = refreshShoppingList;
}
