import { INGREDIENTS, RECIPES } from '../data/catalog.js';
import { MEAL_SLOTS } from '../planner/mealSlots.js';
import {
    addDays,
    addMonths,
    addWeeks,
    sameDay,
    sameMonth,
    startOfDay,
    startOfMonth,
    startOfWeekMonday,
    weekContains,
} from '../services/dateUtils.js';
import {
    computeEntryNutrition,
    computeFullForecast,
    countDayShortfalls,
    dayHasAnyMeal,
    sumDayNutrition,
} from '../services/planIngredients.js';
import { addOrMergeShoppingLines, loadPantry } from '../services/pantryShopping.js';
import {
    dateKey,
    getDayPlan,
    loadPlans,
    newPlanEntryId,
    savePlans,
} from '../services/planStore.js';

const MONTHS_SHORT = [
    'sty', 'lut', 'mar', 'kwi', 'maj', 'cze',
    'lip', 'sie', 'wrz', 'paź', 'lis', 'gru',
];
const WEEKDAYS_SHORT = ['pn', 'wt', 'śr', 'cz', 'pt', 'so', 'nd'];
const WEEKDAYS_LONG = [
    'Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota',
];

const PLANNER_SHEET_BOTTOM_INSET = '5.25rem';
const PLANNER_SHEET_MAX_HEIGHT = '70vh';
const PLANNER_SHEET_OFF_TRANSFORM = `translateY(calc(100% + ${PLANNER_SHEET_BOTTOM_INSET}))`;

function recipesForSlot(slotId) {
    return Object.values(RECIPES).filter((r) => r.allowedSlots.includes(slotId));
}

function isCalendarOnToday(mode, weekStart, monthAnchor, selected) {
    const today = startOfDay(new Date());
    if (!sameDay(selected, today)) return false;
    if (mode === 'week') return weekContains(weekStart, today);
    return sameMonth(monthAnchor, today);
}

function syncTodayButton(mode, weekStart, monthAnchor, selected) {
    const btn = document.getElementById('cal-go-today');
    if (!btn) return;
    const onToday = isCalendarOnToday(mode, weekStart, monthAnchor, selected);
    const active = 'h-6 shrink-0 inline-flex items-center justify-center gap-1 rounded-md border border-gray-200 bg-white px-2 text-[10px] font-semibold text-gray-700 shadow-sm hover:bg-gray-50 hover:text-gray-900 transition-colors';
    const dim = 'h-6 shrink-0 inline-flex items-center justify-center gap-1 rounded-md border border-gray-100 bg-gray-50 px-2 text-[10px] font-semibold text-gray-400 shadow-none cursor-default transition-colors';
    btn.className = onToday ? dim : active;
    btn.disabled = onToday;
}

export function getMealPlannerHTML() {
    return `
    <div id="planner-view" class="hidden flex flex-col h-full absolute inset-0 overflow-hidden bg-gray-50 z-10 pb-24">
        <div class="shrink-0 bg-white border-b border-gray-200 mt-3">
            <div class="px-3 pt-2 pb-1.5 flex items-center gap-1">
                <button type="button" id="cal-prev" class="shrink-0 w-8 h-8 flex items-center justify-center rounded-full border border-gray-200 text-gray-700 hover:bg-gray-50 transition-colors" aria-label="Poprzedni okres">
                    <i class="fas fa-chevron-left text-xs" aria-hidden="true"></i>
                </button>
                <p id="cal-period-label" class="flex-1 min-w-0 text-xs font-medium text-gray-900 text-center tabular-nums leading-none px-1 truncate"></p>
                <button type="button" id="cal-next" class="shrink-0 w-8 h-8 flex items-center justify-center rounded-full border border-gray-200 text-gray-700 hover:bg-gray-50 transition-colors" aria-label="Następny okres">
                    <i class="fas fa-chevron-right text-xs" aria-hidden="true"></i>
                </button>
            </div>
            <div class="px-3 pb-2 flex items-center justify-center">
                <button type="button" id="cal-go-today" title="Dziś" aria-label="Przejdź do dzisiejszego dnia"
                        class="h-6 shrink-0 inline-flex items-center justify-center gap-1 rounded-md border border-gray-200 bg-white px-2.5 text-[10px] font-semibold text-gray-700 shadow-sm hover:bg-gray-50 hover:text-gray-900 transition-colors">
                    <i class="fas fa-calendar-day text-[9px] opacity-70" aria-hidden="true"></i>
                    Dziś
                </button>
            </div>
            <div id="calendar-swipe-zone" style="touch-action: pan-x">
                <div id="calendar-week-wrap" class="px-3 pb-1" style="overflow: hidden; max-height: 10rem; opacity: 1">
                    <div class="grid grid-cols-7 gap-0.5 text-center text-[9px] font-medium text-gray-400 uppercase tracking-wide mb-0.5 leading-none">
                        ${WEEKDAYS_SHORT.map((d) => `<div>${d}</div>`).join('')}
                    </div>
                    <div id="calendar-week-grid" class="grid grid-cols-7 gap-0.5"></div>
                </div>
                <div id="calendar-month-wrap" class="px-3 pb-1" style="overflow: hidden; max-height: 0; opacity: 0">
                    <div class="grid grid-cols-7 gap-0.5 text-center text-[9px] font-medium text-gray-400 uppercase tracking-wide mb-0.5 leading-none">
                        ${WEEKDAYS_SHORT.map((d) => `<div>${d}</div>`).join('')}
                    </div>
                    <div id="calendar-month-grid" class="grid grid-cols-7 gap-0.5"></div>
                </div>
                <div id="calendar-drag-handle" class="flex items-center justify-center pb-2 pt-0.5">
                    <i id="calendar-handle-icon" class="fas fa-chevron-down text-[8px] text-gray-300" aria-hidden="true"></i>
                </div>
            </div>
        </div>

        <div id="planner-scroll" class="flex-1 overflow-y-auto px-4 pt-3 pb-4">
            <div class="flex items-center justify-between mb-2">
                <p id="planner-day-heading" class="text-[13px] font-semibold text-gray-900 tabular-nums"></p>
                <button type="button" id="planner-copy-day" class="shrink-0 text-[11px] font-semibold text-gray-500 hover:text-gray-900 px-2.5 py-1 rounded-lg hover:bg-gray-100 transition-colors flex items-center gap-1.5">
                    <i class="fas fa-copy text-[9px]"></i>Kopiuj dzień
                </button>
            </div>
            <div id="planner-summary-card" class="rounded-xl border border-amber-200/80 bg-gradient-to-br from-amber-50 to-white p-2.5 shadow-sm mb-3">
                <div class="flex items-start justify-between gap-2 mb-2">
                    <div>
                        <p class="text-[10px] font-semibold uppercase tracking-wide text-amber-900/70">Dziś — podsumowanie</p>
                        <p id="planner-summary-kcal" class="text-xl font-bold text-gray-900 tabular-nums leading-tight mt-0.5">0 <span class="text-[13px] font-semibold text-gray-500">kcal</span></p>
                    </div>
                    <button type="button" id="planner-toggle-nutrition" class="shrink-0 flex items-center gap-1 text-[11px] font-semibold text-amber-900/80 hover:text-gray-900 py-1 px-2 rounded-lg hover:bg-amber-100/50 transition-colors" aria-expanded="false">
                        Szczegóły
                        <i class="fas fa-chevron-down text-[9px] transition-transform" id="planner-nutrition-chevron" aria-hidden="true"></i>
                    </button>
                </div>
                <div id="planner-macro-row" class="flex gap-2 mb-0">
                    <div class="flex-1 min-w-0 rounded-lg bg-white/80 border border-amber-100 px-2 py-1.5 text-center">
                        <p class="text-[9px] font-semibold text-gray-500 uppercase">B</p>
                        <p id="planner-macro-p" class="text-xs font-bold text-gray-900 tabular-nums">0 g</p>
                    </div>
                    <div class="flex-1 min-w-0 rounded-lg bg-white/80 border border-amber-100 px-2 py-1.5 text-center">
                        <p class="text-[9px] font-semibold text-gray-500 uppercase">T</p>
                        <p id="planner-macro-f" class="text-xs font-bold text-gray-900 tabular-nums">0 g</p>
                    </div>
                    <div class="flex-1 min-w-0 rounded-lg bg-white/80 border border-amber-100 px-2 py-1.5 text-center">
                        <p class="text-[9px] font-semibold text-gray-500 uppercase">W</p>
                        <p id="planner-macro-c" class="text-xs font-bold text-gray-900 tabular-nums">0 g</p>
                    </div>
                </div>
                <div id="planner-nutrition-details" class="hidden mt-3 pt-3 border-t border-amber-200/60">
                    <ul class="space-y-0 divide-y divide-amber-100/80 text-sm">
                        <li class="flex justify-between py-2 font-bold"><span class="text-gray-800">Kalorie</span><span id="planner-detail-kcal" class="text-gray-900 tabular-nums">0 kcal</span></li>
                        <li class="flex justify-between py-2"><span class="text-gray-700 font-medium">Białko</span><span id="planner-detail-p" class="font-medium text-gray-900 tabular-nums">0 g</span></li>
                        <li class="flex justify-between py-2"><span class="text-gray-700 font-medium">Tłuszcze</span><span id="planner-detail-f" class="font-medium text-gray-900 tabular-nums">0 g</span></li>
                        <li class="flex justify-between py-2"><span class="text-gray-700 font-medium">Węglowodany</span><span id="planner-detail-c" class="font-medium text-gray-900 tabular-nums">0 g</span></li>
                    </ul>
                    <p id="planner-summary-hint" class="text-[11px] text-gray-500 mt-2">Suma z zaplanowanych posiłków (porcje × wartości z przepisu).</p>
                </div>
            </div>
            <button type="button" id="planner-open-ingredients" class="w-full mb-3 flex items-center justify-center gap-2 py-2.5 rounded-xl border border-dashed border-gray-300 bg-white text-[13px] font-semibold text-gray-800 hover:border-gray-400 hover:bg-gray-50 transition-colors">
                <i class="fas fa-shopping-basket text-gray-500 text-xs" aria-hidden="true"></i>
                Składniki na ten dzień
            </button>
            <div id="planner-meal-slots" class="space-y-3 pb-2"></div>
        </div>

        <div id="planner-picker-backdrop" class="absolute left-0 right-0 top-0 z-[45] bg-black/45 hidden opacity-0 transition-opacity duration-200" style="bottom: ${PLANNER_SHEET_BOTTOM_INSET}" aria-hidden="true"></div>
        <div id="planner-picker-sheet" class="absolute left-0 right-0 z-[50] bg-white rounded-t-3xl shadow-[0_-10px_40px_rgba(0,0,0,0.12)] flex flex-col will-change-transform" style="visibility: hidden; bottom: ${PLANNER_SHEET_BOTTOM_INSET}; height: auto; max-height: ${PLANNER_SHEET_MAX_HEIGHT}; transform: ${PLANNER_SHEET_OFF_TRANSFORM}; transition: transform 300ms cubic-bezier(0.32, 0.72, 0, 1)" role="dialog" aria-labelledby="planner-picker-title" aria-modal="true">
            <div class="shrink-0 px-4 pt-3 pb-2 border-b border-gray-100 touch-none cursor-grab active:cursor-grabbing select-none" data-planner-sheet-drag-zone aria-label="Przeciągnij w dół, by zamknąć">
                <div class="w-10 h-1 bg-gray-200 rounded-full mx-auto mb-2.5" aria-hidden="true"></div>
                <h2 id="planner-picker-title" class="text-[15px] font-bold text-gray-900 leading-tight pr-2">Wybierz przepis</h2>
                <p id="planner-picker-sub" class="text-[11px] text-gray-500 mt-1"></p>
            </div>
            <div class="shrink-0 px-4 pt-2 pb-2">
                <input type="text" id="planner-picker-search" class="w-full rounded-xl border border-gray-200 bg-gray-50 px-3 py-2 text-sm outline-none focus:border-gray-400 placeholder:text-gray-400" placeholder="Szukaj przepisu…" />
            </div>
            <div id="planner-picker-list" class="min-h-0 flex-1 overflow-y-auto no-scrollbar px-4 py-2.5 pb-8 space-y-2"></div>
        </div>

        <div id="planner-ing-backdrop" class="absolute left-0 right-0 top-0 z-[45] bg-black/45 hidden opacity-0 transition-opacity duration-200" style="bottom: ${PLANNER_SHEET_BOTTOM_INSET}" aria-hidden="true"></div>
        <div id="planner-ing-sheet" class="absolute left-0 right-0 z-[50] bg-white rounded-t-3xl shadow-[0_-10px_40px_rgba(0,0,0,0.12)] flex flex-col will-change-transform" style="visibility: hidden; bottom: ${PLANNER_SHEET_BOTTOM_INSET}; height: auto; max-height: ${PLANNER_SHEET_MAX_HEIGHT}; transform: ${PLANNER_SHEET_OFF_TRANSFORM}; transition: transform 300ms cubic-bezier(0.32, 0.72, 0, 1)" role="dialog" aria-labelledby="planner-ing-title" aria-modal="true">
            <div class="shrink-0 px-4 pt-3 pb-2 border-b border-gray-100 touch-none cursor-grab active:cursor-grabbing select-none" data-planner-sheet-drag-zone aria-label="Przeciągnij w dół, by zamknąć">
                <div class="w-10 h-1 bg-gray-200 rounded-full mx-auto mb-2.5" aria-hidden="true"></div>
                <h2 id="planner-ing-title" class="text-[15px] font-bold text-gray-900 leading-tight pr-2">Składniki i spiżarnia</h2>
                <p id="planner-ing-sub" class="text-[11px] text-gray-500 mt-1">Porównanie potrzeb z zapasami.</p>
            </div>
            <div id="planner-ing-body" class="min-h-0 flex-1 overflow-y-auto no-scrollbar px-4 py-2 pb-2"></div>
            <div id="planner-ing-footer" class="shrink-0 p-4 pt-2 pb-5 border-t border-gray-100 bg-white space-y-2">
                <button type="button" id="planner-ing-add-all" class="w-full bg-gray-900 hover:bg-black text-white py-3 rounded-xl font-semibold shadow-sm transition-colors text-[13px] flex items-center justify-center gap-2">
                    <i class="fas fa-cart-plus text-xs" aria-hidden="true"></i>
                    Dodaj braki na dziś do listy
                </button>
                <button type="button" id="planner-ing-add-btn" class="hidden w-full border border-gray-200 bg-white text-gray-800 hover:bg-gray-50 py-2.5 rounded-xl font-semibold text-[13px] flex items-center justify-center gap-2 transition-colors">
                    <i class="fas fa-calendar-week text-gray-500 text-[11px]" aria-hidden="true"></i>
                    Dodaj braki na cały tydzień
                </button>
            </div>
        </div>

        <div id="planner-copy-backdrop" class="absolute left-0 right-0 top-0 z-[45] bg-black/45 hidden opacity-0 transition-opacity duration-200" style="bottom: ${PLANNER_SHEET_BOTTOM_INSET}" aria-hidden="true"></div>
        <div id="planner-copy-sheet" class="absolute left-0 right-0 z-[50] bg-white rounded-t-3xl shadow-[0_-10px_40px_rgba(0,0,0,0.12)] flex flex-col will-change-transform" style="visibility: hidden; bottom: ${PLANNER_SHEET_BOTTOM_INSET}; height: auto; max-height: ${PLANNER_SHEET_MAX_HEIGHT}; transform: ${PLANNER_SHEET_OFF_TRANSFORM}; transition: transform 300ms cubic-bezier(0.32, 0.72, 0, 1)" role="dialog" aria-modal="true">
            <div class="shrink-0 px-4 pt-3 pb-2 border-b border-gray-100 touch-none cursor-grab active:cursor-grabbing select-none" data-planner-sheet-drag-zone>
                <div class="w-10 h-1 bg-gray-200 rounded-full mx-auto mb-2.5"></div>
                <h2 class="text-[15px] font-bold text-gray-900 leading-tight">Kopiuj plan dnia</h2>
                <p id="planner-copy-sub" class="text-[11px] text-gray-500 mt-1">Wybierz dzień docelowy.</p>
            </div>
            <div id="planner-copy-list" class="min-h-0 flex-1 overflow-y-auto no-scrollbar px-4 py-2.5 pb-8 space-y-2"></div>
        </div>

        <div id="planner-toast" class="pointer-events-none absolute left-4 right-4 bottom-28 z-[55] opacity-0 translate-y-2 transition-all duration-300" role="status">
            <div class="rounded-xl bg-gray-900 text-white text-sm font-medium px-4 py-3 shadow-lg text-center" id="planner-toast-text"></div>
        </div>
    </div>
    `;
}

function renderWeekGrid(weekStart, selected, plans) {
    const grid = document.getElementById('calendar-week-grid');
    if (!grid) return;

    const cells = [];
    for (let i = 0; i < 7; i++) {
        const day = addDays(weekStart, i);
        const isSel = selected && sameDay(day, selected);
        const isToday = sameDay(day, new Date());
        const hasMeals = dayHasAnyMeal(plans, day);
        cells.push(`
            <button type="button" data-planner-day="${day.getTime()}"
                class="flex flex-col items-center justify-center rounded-md text-xs font-medium transition-colors w-full min-h-10 py-1 gap-0.5 leading-tight
                ${isSel ? 'bg-gray-900 text-white' : 'text-gray-800 hover:bg-gray-100'}
                ${isToday && !isSel ? 'ring-1 ring-inset ring-gray-900' : ''}">
                <span>${day.getDate()}</span>
                ${hasMeals ? `<span class="w-1 h-1 rounded-full ${isSel ? 'bg-white' : 'bg-gray-900'} opacity-80" aria-hidden="true"></span>` : '<span class="w-1 h-1" aria-hidden="true"></span>'}
            </button>
        `);
    }
    grid.innerHTML = cells.join('');
}

function renderMonthGrid(monthAnchor, selected, plans) {
    const grid = document.getElementById('calendar-month-grid');
    if (!grid) return;

    const first = startOfMonth(monthAnchor);
    const startGrid = startOfWeekMonday(first);
    const cells = [];
    for (let i = 0; i < 42; i++) {
        const day = addDays(startGrid, i);
        const inMonth = day.getMonth() === first.getMonth();
        const isSel = selected && sameDay(day, selected);
        const isToday = sameDay(day, new Date());
        const hasMeals = inMonth && dayHasAnyMeal(plans, day);
        cells.push(`
            <button type="button" data-planner-day="${day.getTime()}"
                class="flex flex-col items-center justify-center rounded-md text-xs font-medium transition-colors w-full min-h-10 py-1 gap-0.5 leading-tight
                ${!inMonth ? 'text-gray-300' : (isSel ? 'bg-gray-900 text-white' : 'text-gray-800 hover:bg-gray-100')}
                ${inMonth && isToday && !isSel ? 'ring-1 ring-inset ring-gray-900' : ''}">
                <span>${day.getDate()}</span>
                ${inMonth && hasMeals ? `<span class="w-1 h-1 rounded-full ${isSel ? 'bg-white' : 'bg-gray-900'} opacity-80" aria-hidden="true"></span>` : '<span class="w-1 h-1" aria-hidden="true"></span>'}
            </button>
        `);
    }
    grid.innerHTML = cells.join('');
}

function updatePeriodLabel(mode, weekStart, monthAnchor) {
    const el = document.getElementById('cal-period-label');
    if (!el) return;

    if (mode === 'week') {
        const end = addDays(weekStart, 6);
        const y = weekStart.getFullYear();
        if (weekStart.getMonth() === end.getMonth()) {
            el.textContent = `${weekStart.getDate()}–${end.getDate()} ${MONTHS_SHORT[weekStart.getMonth()]} ${y}`;
        } else {
            el.textContent = `${weekStart.getDate()} ${MONTHS_SHORT[weekStart.getMonth()]} – ${end.getDate()} ${MONTHS_SHORT[end.getMonth()]} ${y}`;
        }
    } else {
        const m = monthAnchor.getMonth();
        const y = monthAnchor.getFullYear();
        const monthLong = [
            'Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec',
            'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień',
        ][m];
        el.textContent = `${monthLong} ${y}`;
    }
}

function syncModeToggle(mode) {
    const weekWrap = document.getElementById('calendar-week-wrap');
    const monthWrap = document.getElementById('calendar-month-wrap');
    const handleIcon = document.getElementById('calendar-handle-icon');

    if (weekWrap) {
        weekWrap.style.maxHeight = mode === 'week' ? '10rem' : '0';
        weekWrap.style.opacity = mode === 'week' ? '1' : '0';
    }
    if (monthWrap) {
        monthWrap.style.maxHeight = mode === 'month' ? '25rem' : '0';
        monthWrap.style.opacity = mode === 'month' ? '1' : '0';
    }
    if (handleIcon) {
        handleIcon.className = mode === 'week'
            ? 'fas fa-chevron-down text-[8px] text-gray-300'
            : 'fas fa-chevron-up text-[8px] text-gray-300';
    }
}

function bindDayClicks(container, state, rerender) {
    container?.addEventListener('click', (e) => {
        const btn = e.target.closest('[data-planner-day]');
        if (!btn) return;
        const ts = Number(btn.getAttribute('data-planner-day'));
        state.selected = new Date(ts);
        rerender();
    });
}

function bindCalendarSwipeGesture(state, rerender) {
    const zone = document.getElementById('calendar-swipe-zone');
    if (!zone) return;

    let startY = 0;
    let ptrId = null;
    let moved = false;

    zone.addEventListener('pointerdown', (e) => {
        if (ptrId !== null) return;
        startY = e.clientY;
        ptrId = e.pointerId;
        moved = false;
    });

    zone.addEventListener('pointermove', (e) => {
        if (e.pointerId !== ptrId) return;
        if (Math.abs(e.clientY - startY) > 10) moved = true;
    });

    zone.addEventListener('pointerup', (e) => {
        if (e.pointerId !== ptrId) return;
        const dy = e.clientY - startY;
        ptrId = null;

        if (!moved || Math.abs(dy) < 30) return;

        let switched = false;
        if (state.mode === 'week' && dy > 30) {
            state.mode = 'month';
            state.monthAnchor = startOfMonth(state.selected);
            switched = true;
        } else if (state.mode === 'month' && dy < -30) {
            state.mode = 'week';
            state.weekStart = startOfWeekMonday(state.selected);
            switched = true;
        }

        if (switched) {
            zone.addEventListener('click', (ev) => {
                ev.stopPropagation();
                ev.preventDefault();
            }, { capture: true, once: true });
            rerender();
        }
    });

    zone.addEventListener('pointercancel', () => {
        ptrId = null;
        moved = false;
    });
}

function showPlannerToast(message) {
    const wrap = document.getElementById('planner-toast');
    const text = document.getElementById('planner-toast-text');
    if (!wrap || !text) return;
    text.textContent = message;
    wrap.classList.remove('opacity-0', 'translate-y-2');
    wrap.classList.add('opacity-100', 'translate-y-0');
    clearTimeout(showPlannerToast._t);
    showPlannerToast._t = setTimeout(() => {
        wrap.classList.add('opacity-0', 'translate-y-2');
        wrap.classList.remove('opacity-100', 'translate-y-0');
    }, 2600);
}

function openSheet(backdrop, sheet) {
    if (!backdrop || !sheet) return;
    sheet.style.visibility = 'visible';
    sheet.style.transition = 'transform 300ms cubic-bezier(0.32, 0.72, 0, 1)';
    sheet.style.transform = 'translateY(0)';
    backdrop.classList.remove('hidden');
    requestAnimationFrame(() => {
        backdrop.classList.remove('opacity-0');
    });
}

function closeSheet(backdrop, sheet) {
    if (!backdrop || !sheet) return;
    sheet.style.transition = 'transform 300ms cubic-bezier(0.32, 0.72, 0, 1)';
    sheet.style.transform = PLANNER_SHEET_OFF_TRANSFORM;
    backdrop.classList.add('opacity-0');
    setTimeout(() => {
        backdrop.classList.add('hidden');
        sheet.style.visibility = 'hidden';
    }, 300);
}

/** Zamykanie panelu: przeciągnięcie nagłówka w dół (pointer). */
function bindPlannerSheetDragClose(sheet, closeFn) {
    const zone = sheet.querySelector('[data-planner-sheet-drag-zone]');
    if (!zone || !sheet) return;

    let startY = 0;
    let pulling = false;
    let ptrId = null;

    const resetVisual = () => {
        sheet.style.transition = 'transform 300ms cubic-bezier(0.32, 0.72, 0, 1)';
        sheet.style.transform = 'translateY(0)';
    };

    zone.addEventListener('pointerdown', (e) => {
        if (e.pointerType === 'mouse' && e.button !== 0) return;
        pulling = true;
        ptrId = e.pointerId;
        startY = e.clientY;
        sheet.style.transition = 'none';
        zone.setPointerCapture(e.pointerId);
    });

    zone.addEventListener('pointermove', (e) => {
        if (!pulling || e.pointerId !== ptrId) return;
        const dy = Math.max(0, e.clientY - startY);
        sheet.style.transform = `translateY(${dy}px)`;
    });

    zone.addEventListener('pointerup', (e) => {
        if (!pulling || e.pointerId !== ptrId) return;
        const dy = e.clientY - startY;
        pulling = false;
        ptrId = null;
        try {
            zone.releasePointerCapture(e.pointerId);
        } catch {
            /* ignore */
        }
        if (dy > 56) {
            closeFn();
            return;
        }
        resetVisual();
    });

    zone.addEventListener('pointercancel', () => {
        pulling = false;
        ptrId = null;
        resetVisual();
    });
}

function renderDayContent(state) {
    const sel = state.selected;
    const heading = document.getElementById('planner-day-heading');
    if (heading) {
        const wd = WEEKDAYS_LONG[sel.getDay()];
        heading.textContent = `${wd}, ${sel.getDate()} ${MONTHS_SHORT[sel.getMonth()]}`;
    }

    const dayPlan = getDayPlan(state.plans, sel);
    const totals = sumDayNutrition(dayPlan);

    const kcalEl = document.getElementById('planner-summary-kcal');
    if (kcalEl) {
        kcalEl.innerHTML = totals.mealCount === 0
            ? `— <span class="text-sm font-semibold text-gray-500">kcal</span>`
            : `${totals.kcal} <span class="text-sm font-semibold text-gray-500">kcal</span>`;
    }
    const fmt = (n) => `${n} g`;
    document.getElementById('planner-macro-p').textContent = totals.mealCount ? fmt(totals.protein) : '—';
    document.getElementById('planner-macro-f').textContent = totals.mealCount ? fmt(totals.fat) : '—';
    document.getElementById('planner-macro-c').textContent = totals.mealCount ? fmt(totals.carbs) : '—';

    document.getElementById('planner-detail-kcal').textContent = `${totals.kcal} kcal`;
    document.getElementById('planner-detail-p').textContent = fmt(totals.protein);
    document.getElementById('planner-detail-f').textContent = fmt(totals.fat);
    document.getElementById('planner-detail-c').textContent = fmt(totals.carbs);

    const ingBtn = document.getElementById('planner-open-ingredients');
    if (ingBtn) {
        const noMeals = totals.mealCount === 0;
        ingBtn.disabled = noMeals;
        ingBtn.classList.toggle('opacity-50', noMeals);
        ingBtn.classList.toggle('cursor-not-allowed', noMeals);
        if (!noMeals) {
            const shortCount = countDayShortfalls(dayPlan, loadPantry());
            if (shortCount > 0) {
                ingBtn.innerHTML = `<i class="fas fa-shopping-basket text-xs" aria-hidden="true"></i>
                    Składniki na ten dzień
                    <span class="ml-auto bg-red-500 text-white text-[10px] font-bold rounded-full w-5 h-5 inline-flex items-center justify-center">${shortCount}</span>`;
            } else {
                ingBtn.innerHTML = `<i class="fas fa-check-circle text-emerald-500 text-xs" aria-hidden="true"></i>
                    Składniki na ten dzień
                    <span class="ml-auto text-[10px] font-semibold text-emerald-600">OK</span>`;
            }
        } else {
            ingBtn.innerHTML = `<i class="fas fa-shopping-basket text-gray-500 text-xs" aria-hidden="true"></i> Składniki na ten dzień`;
        }
    }

    const slotsRoot = document.getElementById('planner-meal-slots');
    if (!slotsRoot) return;

    const skipped = dayPlan._skipped || {};

    slotsRoot.innerHTML = MEAL_SLOTS.map((slot) => {
        const isSkipped = skipped[slot.id] === true;
        const entries = isSkipped ? [] : (Array.isArray(dayPlan[slot.id]) ? dayPlan[slot.id] : []);

        let slotKcal = 0;
        entries.forEach((entry) => {
            if (entry?.recipeId && RECIPES[entry.recipeId]) slotKcal += computeEntryNutrition(entry).kcal;
        });
        const kcalBadge = slotKcal > 0
            ? `<span class="text-[10px] font-semibold text-amber-600 tabular-nums shrink-0 ml-auto">${slotKcal} kcal</span>`
            : '';
        const countLabel = entries.length > 1
            ? `<span class="text-[10px] font-semibold text-gray-400 tabular-nums shrink-0">${entries.length} dania</span>`
            : '';

        const entryCards = entries.map((entry) => {
            const recipe = entry && entry.recipeId ? RECIPES[entry.recipeId] : null;
            if (!recipe) return '';
            const servings = Math.max(1, Number(entry.servings) || 1);
            const entryN = computeEntryNutrition(entry);
            const eid = escapeHtml(entry.id);
            const hasCustom = (entry.excludedIngredients?.length > 0) ||
                (entry.amountOverrides && Object.keys(entry.amountOverrides).length > 0) ||
                (entry.addedIngredients?.length > 0) ||
                (entry.substitutions && Object.keys(entry.substitutions).length > 0);
            const customDot = hasCustom ? '<span class="w-1.5 h-1.5 rounded-full bg-amber-400 inline-block shrink-0 ml-1"></span>' : '';
            const servLabel = servings > 1 ? `<span class="mx-1.5 text-gray-300">·</span>×${servings}` : '';
            return `
            <div class="rounded-lg border border-gray-200 bg-white p-2 shadow-sm" data-slot-id="${slot.id}" data-entry-id="${eid}">
                <div class="flex items-start justify-between gap-2">
                    <div class="flex items-center gap-2 min-w-0 cursor-pointer planner-open-recipe" data-recipe-id="${escapeHtml(recipe.id)}">
                        <div class="w-8 h-8 rounded-lg bg-[#d4d4d4] overflow-hidden shrink-0">
                            ${recipe.image
                                ? `<img src="${escapeHtml(recipe.image)}" alt="" class="w-full h-full object-cover">`
                                : `<span class="w-full h-full flex items-center justify-center text-white text-[8px] font-medium">${escapeHtml(recipe.thumbLabel)}</span>`}
                        </div>
                        <div class="min-w-0">
                            <div class="flex items-center"><p class="text-[13px] font-bold text-gray-900 truncate underline decoration-1 underline-offset-2">${escapeHtml(recipe.title)}</p>${customDot}</div>
                            <p class="text-[11px] text-gray-500 mt-0.5 tabular-nums">
                                <i class="fas fa-clock text-gray-400 mr-0.5" aria-hidden="true"></i>${recipe.minutes} min
                                <span class="mx-1.5 text-gray-300">·</span>
                                <i class="fas fa-fire text-gray-400 mr-0.5" aria-hidden="true"></i>${entryN.kcal} kcal${servLabel}
                            </p>
                        </div>
                    </div>
                    <div class="flex items-center gap-1 shrink-0">
                        <button type="button" class="planner-edit-meal w-6 h-6 rounded-full border border-gray-200 text-gray-400 hover:text-gray-900 hover:border-gray-400 hover:bg-gray-50 flex items-center justify-center transition-colors" data-slot-id="${slot.id}" data-entry-id="${eid}" aria-label="Edytuj ten przepis">
                            <i class="fas fa-pencil text-[9px]" aria-hidden="true"></i>
                        </button>
                        <button type="button" class="planner-clear-meal w-6 h-6 rounded-full border border-gray-200 text-gray-400 hover:text-red-600 hover:border-red-200 hover:bg-red-50 transition-colors flex items-center justify-center" data-slot-id="${slot.id}" data-entry-id="${eid}" aria-label="Usuń ten przepis">
                            <i class="fas fa-times text-[9px]" aria-hidden="true"></i>
                        </button>
                    </div>
                </div>
            </div>`;
        }).join('');

        if (isSkipped) {
            return `
            <div class="rounded-xl border border-gray-200 bg-white shadow-sm overflow-hidden opacity-60" data-slot-id="${slot.id}">
                <div class="flex items-center gap-2 px-3 py-2 border-b border-gray-100 bg-gray-50/90">
                    <span class="w-7 h-7 rounded-lg bg-gray-100 flex items-center justify-center text-gray-400 shrink-0">
                        <i class="fas ${slot.icon} text-[13px]" aria-hidden="true"></i>
                    </span>
                    <span class="text-[13px] font-semibold text-gray-400 truncate min-w-0 flex-1">${slot.label}</span>
                </div>
                <div class="p-2.5 flex items-center justify-between">
                    <span class="text-xs text-gray-400 italic"><i class="fas fa-forward text-[9px] mr-1.5"></i>Pominięto</span>
                    <button type="button" class="planner-unskip text-[11px] font-semibold text-gray-500 hover:text-gray-900 px-2 py-1 rounded-lg hover:bg-gray-100 transition-colors" data-slot-id="${slot.id}">Cofnij</button>
                </div>
            </div>`;
        }

        const addLabel = entries.length === 0 ? 'Dodaj przepis' : 'Dodaj kolejny';
        const addClasses = entries.length === 0
            ? 'planner-add-meal flex-1 py-2 rounded-lg border border-dashed border-gray-200 text-[13px] font-semibold text-gray-700 hover:bg-gray-50 hover:border-gray-300 transition-colors'
            : 'planner-add-meal w-full py-1.5 rounded-lg border border-dashed border-gray-200 text-xs font-semibold text-gray-600 hover:bg-gray-50 hover:border-gray-300 transition-colors';

        const skipBtn = entries.length === 0
            ? `<button type="button" class="planner-skip-meal shrink-0 py-2 px-3 rounded-lg text-[11px] font-semibold text-gray-400 hover:text-gray-600 hover:bg-gray-100 transition-colors" data-slot-id="${slot.id}"><i class="fas fa-forward text-[9px] mr-1"></i>Pomijam</button>`
            : '';

        const bottomRow = entries.length === 0
            ? `<div class="flex gap-2">${`<button type="button" class="${addClasses}" data-slot-id="${slot.id}"><i class="fas fa-plus text-[10px] mr-1 opacity-70" aria-hidden="true"></i>${addLabel}</button>`}${skipBtn}</div>`
            : `<button type="button" class="${addClasses}" data-slot-id="${slot.id}"><i class="fas fa-plus text-[10px] mr-1 opacity-70" aria-hidden="true"></i>${addLabel}</button>`;

        return `
        <div class="rounded-xl border border-gray-200 bg-white shadow-sm overflow-hidden" data-slot-id="${slot.id}">
            <div class="flex items-center gap-2 px-3 py-2 border-b border-gray-100 bg-gray-50/90">
                <span class="w-7 h-7 rounded-lg bg-gray-100 flex items-center justify-center text-gray-500 shrink-0">
                    <i class="fas ${slot.icon} text-[13px]" aria-hidden="true"></i>
                </span>
                <span class="text-[13px] font-semibold text-gray-900 truncate min-w-0 flex-1">${slot.label}</span>
                ${countLabel}
                ${kcalBadge}
            </div>
            <div class="p-2.5 space-y-2">
                ${entryCards}
                ${bottomRow}
            </div>
        </div>`;
    }).join('');
}

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

function getRecentRecipeIds(plans, limit = 5) {
    const seen = new Map();
    const keys = Object.keys(plans).sort().reverse();
    for (const key of keys) {
        const day = plans[key];
        if (!day) continue;
        for (const slotId of Object.keys(day)) {
            if (slotId === '_skipped') continue;
            const entries = day[slotId];
            if (!Array.isArray(entries)) continue;
            for (const e of entries) {
                if (e?.recipeId && RECIPES[e.recipeId] && !seen.has(e.recipeId)) {
                    seen.set(e.recipeId, true);
                    if (seen.size >= limit) return [...seen.keys()];
                }
            }
        }
    }
    return [...seen.keys()];
}

function recipeCardHtml(r) {
    return `
        <button type="button" class="planner-pick-recipe w-full flex gap-2.5 p-2.5 rounded-xl border border-gray-200 bg-gray-50/80 hover:border-gray-900 hover:bg-white text-left transition-all" data-recipe-id="${r.id}">
            <div class="w-11 h-11 rounded-lg bg-[#d4d4d4] overflow-hidden shrink-0">
                ${r.image
                    ? `<img src="${escapeHtml(r.image)}" alt="" class="w-full h-full object-cover">`
                    : `<span class="w-full h-full flex items-center justify-center text-white text-[9px] font-medium">${escapeHtml(r.thumbLabel)}</span>`}
            </div>
            <div class="min-w-0 flex-1 py-0.5">
                <p class="text-[13px] font-bold text-gray-900 line-clamp-2">${escapeHtml(r.title)}</p>
                <p class="text-[11px] text-gray-500 mt-1 tabular-nums">
                    <i class="fas fa-fire text-gray-400 mr-0.5" aria-hidden="true"></i>${r.nutritionPerServing.kcal} kcal
                    <span class="mx-1 text-gray-300">·</span>
                    <i class="fas fa-clock text-gray-400 mr-0.5" aria-hidden="true"></i>${r.minutes} min
                </p>
            </div>
        </button>`;
}

let _pickerSlotRecipes = [];
let _pickerPlans = {};

function renderPickerList(slotId, plans, query = '') {
    const slot = MEAL_SLOTS.find((s) => s.id === slotId);
    const list = document.getElementById('planner-picker-list');
    const title = document.getElementById('planner-picker-title');
    const sub = document.getElementById('planner-picker-sub');
    if (!list || !title || !sub) return;

    title.textContent = 'Wybierz przepis';
    sub.textContent = slot ? `Dla: ${slot.label}` : '';

    const allRecipes = recipesForSlot(slotId);
    _pickerSlotRecipes = allRecipes;
    _pickerPlans = plans;

    const q = query.trim().toLowerCase();
    const filtered = q
        ? allRecipes.filter((r) => r.title.toLowerCase().includes(q) || (r.tags || []).some((t) => t.toLowerCase().includes(q)))
        : allRecipes;

    if (filtered.length === 0 && q) {
        list.innerHTML = '<p class="text-sm text-gray-500 text-center py-6">Brak wyników.</p>';
        return;
    }
    if (filtered.length === 0) {
        list.innerHTML = '<p class="text-sm text-gray-500 text-center py-6">Brak dopasowanych przepisów.</p>';
        return;
    }

    let html = '';

    if (!q) {
        const recentIds = getRecentRecipeIds(plans);
        const recentInSlot = recentIds.map((id) => RECIPES[id]).filter((r) => r && r.allowedSlots.includes(slotId));
        if (recentInSlot.length > 0) {
            html += `<p class="text-[10px] font-bold text-gray-400 uppercase tracking-wider px-0.5 pt-1 pb-1"><i class="fas fa-history text-[9px] mr-1"></i>Ostatnio używane</p>`;
            html += recentInSlot.map(recipeCardHtml).join('');
            html += `<div class="border-t border-gray-100 my-2"></div>`;
            html += `<p class="text-[10px] font-bold text-gray-400 uppercase tracking-wider px-0.5 pt-1 pb-1">Wszystkie</p>`;
        }
    }

    html += filtered.map(recipeCardHtml).join('');
    list.innerHTML = html;
}

function plIngredientWord(n) {
    if (n === 1) return 'składnik';
    const m10 = n % 10;
    const m100 = n % 100;
    if (m10 >= 2 && m10 <= 4 && (m100 < 12 || m100 > 14)) return 'składniki';
    return 'składników';
}

function updateIngButtons(state) {
    const btn1 = document.getElementById('planner-ing-add-all');
    const btn2 = document.getElementById('planner-ing-add-btn');

    const todayCount = (state._todayShortfalls || []).length;
    const allCount = (state._allForecastShortfalls || []).length;

    if (btn1) {
        if (todayCount > 0) {
            btn1.classList.remove('hidden');
            btn1.disabled = false;
            btn1.innerHTML = `<i class="fas fa-cart-plus text-xs" aria-hidden="true"></i> Dodaj braki na dziś do listy`;
        } else {
            btn1.classList.add('hidden');
        }
    }
    if (btn2) {
        if (allCount > todayCount) {
            btn2.classList.remove('hidden');
            btn2.innerHTML = `<i class="fas fa-calendar-week text-gray-500 text-[11px]" aria-hidden="true"></i> Dodaj braki na cały tydzień`;
        } else {
            btn2.classList.add('hidden');
        }
    }
}

function renderIngredientsSheet(state) {
    const body = document.getElementById('planner-ing-body');
    const titleEl = document.getElementById('planner-ing-title');
    const subEl = document.getElementById('planner-ing-sub');
    if (!body) return;

    const pantry = loadPantry();
    const forecast = computeFullForecast(state.plans, pantry, state.selected);

    const today = forecast.length > 0 && forecast[0].dayIndex === 0 ? forecast[0] : null;
    const upcoming = forecast.filter((d) => d.dayIndex > 0 && d.hasShortfall);

    state._todayShortfalls = today ? today.items.filter((it) => !it.enough) : [];
    state._allForecastShortfalls = [];
    for (const d of forecast) {
        for (const it of d.items) {
            if (!it.enough) state._allForecastShortfalls.push(it);
        }
    }

    if (titleEl) {
        const wd = WEEKDAYS_LONG[state.selected.getDay()];
        titleEl.textContent = `${wd}, ${state.selected.getDate()} ${MONTHS_SHORT[state.selected.getMonth()]} — składniki`;
    }
    if (subEl) subEl.textContent = 'Porównanie potrzeb z zapasami w spiżarni.';

    if (!today || today.items.length === 0) {
        body.innerHTML = '<p class="text-sm text-gray-500 text-center py-8">Najpierw zaplanuj posiłki.</p>';
        updateIngButtons(state);
        return;
    }

    const shortItems = today.items.filter((it) => !it.enough);
    const okItems = today.items.filter((it) => it.enough);
    let html = '';

    if (shortItems.length === 0) {
        html += `<div class="rounded-xl bg-emerald-50 border border-emerald-200/80 p-3 mb-4 flex items-center gap-2.5">
            <div class="w-8 h-8 rounded-full bg-emerald-100 flex items-center justify-center shrink-0">
                <i class="fas fa-check text-emerald-600 text-sm"></i>
            </div>
            <div>
                <p class="text-[13px] font-semibold text-emerald-800">Wszystko masz w spiżarni</p>
                <p class="text-[11px] text-emerald-600/80">${today.items.length} ${plIngredientWord(today.items.length)} — zapasy wystarczą</p>
            </div>
        </div>`;
    } else {
        html += `<div class="rounded-xl bg-red-50 border border-red-200/80 p-3 mb-4 flex items-center gap-2.5">
            <div class="w-8 h-8 rounded-full bg-red-100 flex items-center justify-center shrink-0">
                <i class="fas fa-exclamation text-red-500 text-sm"></i>
            </div>
            <div>
                <p class="text-[13px] font-semibold text-red-800">${shortItems.length} ${plIngredientWord(shortItems.length)} do kupienia</p>
                <p class="text-[11px] text-red-600/80">Brakuje składników na zaplanowane posiłki</p>
            </div>
        </div>`;
    }

    if (shortItems.length > 0) {
        html += `<div class="mb-5">
            <p class="text-[10px] font-bold text-red-400 uppercase tracking-wider mb-2 px-0.5">
                <i class="fas fa-cart-shopping text-[9px] mr-1"></i>Do kupienia
            </p>
            <ul class="border border-red-100/80 rounded-xl overflow-hidden bg-white divide-y divide-red-50">
                ${shortItems.map((ing) => `
                <li class="flex items-start gap-3 py-3 px-3">
                    <div class="w-2 h-2 rounded-full bg-red-400 mt-1.5 shrink-0"></div>
                    <div class="flex-1 min-w-0">
                        <p class="text-[13px] font-semibold text-gray-900">${escapeHtml(ing.name)}</p>
                        <p class="text-[11px] text-gray-500 mt-0.5">
                            potrzeba <span class="font-medium text-gray-700">${formatAmount(ing.amount)} ${escapeHtml(ing.pantryUnit)}</span>
                            <span class="mx-1 text-gray-300">&middot;</span>
                            w spiżarni <span class="font-medium ${ing.pantryQty > 0 ? 'text-amber-600' : 'text-gray-400'}">${ing.pantryQty > 0 ? formatAmount(ing.pantryQty) + ' ' + escapeHtml(ing.pantryUnit) : 'brak'}</span>
                        </p>
                    </div>
                    <div class="text-right shrink-0 pt-0.5">
                        <p class="text-[13px] font-bold text-red-600 tabular-nums leading-tight">&minus;${formatAmount(ing.shortfall)}</p>
                        <p class="text-[9px] text-red-400 font-medium">${escapeHtml(ing.pantryUnit)}</p>
                    </div>
                </li>`).join('')}
            </ul>
        </div>`;
    }

    if (okItems.length > 0) {
        html += `<div class="mb-5">
            <p class="text-[10px] font-bold text-emerald-500 uppercase tracking-wider mb-2 px-0.5">
                <i class="fas fa-check text-[9px] mr-1"></i>W spiżarni
            </p>
            <ul class="border border-gray-100 rounded-xl overflow-hidden bg-white divide-y divide-gray-50">
                ${okItems.map((ing) => `
                <li class="flex items-start gap-3 py-2.5 px-3">
                    <div class="w-2 h-2 rounded-full bg-emerald-400 mt-1.5 shrink-0"></div>
                    <div class="flex-1 min-w-0">
                        <p class="text-[13px] font-medium text-gray-700">${escapeHtml(ing.name)}</p>
                        <p class="text-[11px] text-gray-400 mt-0.5">
                            potrzeba <span class="font-medium">${formatAmount(ing.amount)} ${escapeHtml(ing.pantryUnit)}</span>
                            <span class="mx-1 text-gray-300">&middot;</span>
                            masz <span class="font-medium text-emerald-600">${formatAmount(ing.pantryQty)} ${escapeHtml(ing.pantryUnit)}</span>
                        </p>
                    </div>
                </li>`).join('')}
            </ul>
        </div>`;
    }

    if (upcoming.length > 0) {
        html += `<div class="mb-2">
            <p class="text-[10px] font-bold text-amber-500 uppercase tracking-wider mb-2 px-0.5">
                <i class="fas fa-calendar-alt text-[9px] mr-1"></i>Nadchodzące braki
            </p>
            <div class="space-y-2">
                ${upcoming.map((day) => {
                    const wd = WEEKDAYS_LONG[day.date.getDay()];
                    const label = `${wd}, ${day.date.getDate()} ${MONTHS_SHORT[day.date.getMonth()]}`;
                    const shorts = day.items.filter((it) => !it.enough);
                    return `<div class="rounded-xl border border-amber-200/80 bg-amber-50/50 p-3">
                        <p class="text-[12px] font-semibold text-amber-900">
                            <i class="fas fa-calendar-day text-[10px] mr-1.5 text-amber-500"></i>${escapeHtml(label)}
                        </p>
                        <ul class="mt-2 space-y-1.5">
                            ${shorts.map((it) => `
                            <li class="flex items-center justify-between text-[11px]">
                                <span class="font-medium text-amber-900">${escapeHtml(it.name)}</span>
                                <span class="font-semibold text-red-600 tabular-nums">&minus;${formatAmount(it.shortfall)} ${escapeHtml(it.pantryUnit)}</span>
                            </li>`).join('')}
                        </ul>
                    </div>`;
                }).join('')}
            </div>
        </div>`;
    }

    body.innerHTML = html;
    updateIngButtons(state);
}

function formatAmount(n) {
    return Number.isInteger(n) ? String(n) : String(n);
}

function seedDemoIfEmpty(plans) {
    const todayKey = dateKey(new Date());
    if (Object.keys(plans).length > 0) return plans;
    return {
        ...plans,
        [todayKey]: {
            sniadanie: [{ id: newPlanEntryId(), recipeId: 'jajecznica', servings: 1 }],
            obiad: [{ id: newPlanEntryId(), recipeId: 'makaron_ricotta', servings: 1 }],
            kolacja: [{ id: newPlanEntryId(), recipeId: 'kanapka_losos', servings: 1 }],
        },
    };
}

export function setupMealPlanner() {
    let plans = loadPlans();
    plans = seedDemoIfEmpty(plans);
    savePlans(plans);

    const state = {
        mode: 'week',
        weekStart: startOfWeekMonday(new Date()),
        monthAnchor: startOfDay(new Date()),
        selected: startOfDay(new Date()),
        plans,
        nutritionExpanded: false,
        pickerSlot: null,
    };

    const weekGrid = document.getElementById('calendar-week-grid');
    const monthGrid = document.getElementById('calendar-month-grid');

    const pickerBackdrop = document.getElementById('planner-picker-backdrop');
    const pickerSheet = document.getElementById('planner-picker-sheet');
    const ingBackdrop = document.getElementById('planner-ing-backdrop');
    const ingSheet = document.getElementById('planner-ing-sheet');
    const copyBackdrop = document.getElementById('planner-copy-backdrop');
    const copySheet = document.getElementById('planner-copy-sheet');

    const rerender = () => {
        syncModeToggle(state.mode);
        updatePeriodLabel(state.mode, state.weekStart, state.monthAnchor);
        syncTodayButton(state.mode, state.weekStart, state.monthAnchor, state.selected);
        renderWeekGrid(state.weekStart, state.selected, state.plans);
        renderMonthGrid(state.monthAnchor, state.selected, state.plans);
        renderDayContent(state);
    };

    const persist = () => {
        savePlans(state.plans);
        rerender();
    };

    bindDayClicks(weekGrid?.parentElement, state, rerender);
    bindDayClicks(monthGrid?.parentElement, state, rerender);

    document.getElementById('cal-prev')?.addEventListener('click', () => {
        if (state.mode === 'week') {
            state.weekStart = addWeeks(state.weekStart, -1);
            if (!weekContains(state.weekStart, state.selected)) {
                state.selected = new Date(state.weekStart);
            }
        } else {
            state.monthAnchor = addMonths(state.monthAnchor, -1);
            if (!sameMonth(state.monthAnchor, state.selected)) {
                state.selected = startOfMonth(state.monthAnchor);
            }
        }
        rerender();
    });

    document.getElementById('cal-next')?.addEventListener('click', () => {
        if (state.mode === 'week') {
            state.weekStart = addWeeks(state.weekStart, 1);
            if (!weekContains(state.weekStart, state.selected)) {
                state.selected = new Date(state.weekStart);
            }
        } else {
            state.monthAnchor = addMonths(state.monthAnchor, 1);
            if (!sameMonth(state.monthAnchor, state.selected)) {
                state.selected = startOfMonth(state.monthAnchor);
            }
        }
        rerender();
    });

    document.getElementById('cal-go-today')?.addEventListener('click', () => {
        const today = startOfDay(new Date());
        state.selected = today;
        state.weekStart = startOfWeekMonday(today);
        state.monthAnchor = startOfMonth(today);
        rerender();
    });

    document.getElementById('planner-toggle-nutrition')?.addEventListener('click', () => {
        state.nutritionExpanded = !state.nutritionExpanded;
        const details = document.getElementById('planner-nutrition-details');
        const chev = document.getElementById('planner-nutrition-chevron');
        const btn = document.getElementById('planner-toggle-nutrition');
        if (details) details.classList.toggle('hidden', !state.nutritionExpanded);
        if (chev) chev.classList.toggle('rotate-180', state.nutritionExpanded);
        if (btn) btn.setAttribute('aria-expanded', state.nutritionExpanded ? 'true' : 'false');
    });

    document.getElementById('planner-meal-slots')?.addEventListener('click', (e) => {
        const skipBtn = e.target.closest('.planner-skip-meal');
        if (skipBtn) {
            const slotId = skipBtn.getAttribute('data-slot-id');
            if (!slotId) return;
            const key = dateKey(state.selected);
            if (!state.plans[key]) state.plans[key] = {};
            if (!state.plans[key]._skipped) state.plans[key]._skipped = {};
            state.plans[key]._skipped[slotId] = true;
            persist();
            return;
        }
        const unskipBtn = e.target.closest('.planner-unskip');
        if (unskipBtn) {
            const slotId = unskipBtn.getAttribute('data-slot-id');
            if (!slotId) return;
            const key = dateKey(state.selected);
            if (state.plans[key]?._skipped) {
                delete state.plans[key]._skipped[slotId];
                if (Object.keys(state.plans[key]._skipped).length === 0) delete state.plans[key]._skipped;
                if (Object.keys(state.plans[key]).length === 0) delete state.plans[key];
            }
            persist();
            return;
        }
        const openRecipe = e.target.closest('.planner-open-recipe');
        if (openRecipe) {
            const recipeId = openRecipe.getAttribute('data-recipe-id');
            if (recipeId && window.openRecipeDetail) {
                window.openRecipeDetail(recipeId);
            }
            return;
        }
        const addBtn = e.target.closest('.planner-add-meal');
        if (addBtn) {
            const slotId = addBtn.getAttribute('data-slot-id');
            state.pickerSlot = slotId;
            const searchInput = document.getElementById('planner-picker-search');
            if (searchInput) searchInput.value = '';
            renderPickerList(slotId, state.plans);
            openSheet(pickerBackdrop, pickerSheet);
            return;
        }
        const editBtn = e.target.closest('.planner-edit-meal');
        if (editBtn) {
            const slotId = editBtn.getAttribute('data-slot-id');
            const entryId = editBtn.getAttribute('data-entry-id');
            const key = dateKey(state.selected);
            const arr = state.plans[key]?.[slotId];
            if (!Array.isArray(arr)) return;
            const entry = arr.find((x) => x && x.id === entryId);
            if (!entry) return;
            window.openMealPlanEditor?.({
                mode: 'edit',
                recipeId: entry.recipeId,
                date: state.selected,
                slotId,
                entry,
            });
            return;
        }
        const clearBtn = e.target.closest('.planner-clear-meal');
        if (clearBtn) {
            const slotId = clearBtn.getAttribute('data-slot-id');
            const entryId = clearBtn.getAttribute('data-entry-id');
            const key = dateKey(state.selected);
            const arr = state.plans[key]?.[slotId];
            if (!Array.isArray(arr) || !entryId) return;
            const next = arr.filter((x) => x && x.id !== entryId);
            if (!state.plans[key]) state.plans[key] = {};
            if (next.length === 0) delete state.plans[key][slotId];
            else state.plans[key][slotId] = next;
            if (Object.keys(state.plans[key]).length === 0) delete state.plans[key];
            persist();
            return;
        }
    });

    const closePicker = () => {
        state.pickerSlot = null;
        closeSheet(pickerBackdrop, pickerSheet);
    };

    document.getElementById('planner-picker-search')?.addEventListener('input', (e) => {
        if (state.pickerSlot) {
            renderPickerList(state.pickerSlot, state.plans, e.target.value);
        }
    });

    bindPlannerSheetDragClose(pickerSheet, closePicker);
    bindPlannerSheetDragClose(ingSheet, () => closeSheet(ingBackdrop, ingSheet));

    pickerBackdrop?.addEventListener('click', closePicker);

    document.getElementById('planner-picker-list')?.addEventListener('click', (e) => {
        const pick = e.target.closest('.planner-pick-recipe');
        if (!pick || !state.pickerSlot) return;
        const recipeId = pick.getAttribute('data-recipe-id');
        if (!recipeId || !RECIPES[recipeId]) return;
        const slotId = state.pickerSlot;
        closePicker();
        setTimeout(() => {
            window.openMealPlanEditor?.({
                mode: 'add',
                recipeId,
                date: state.selected,
                slotId,
            });
        }, 320);
    });

    document.getElementById('planner-open-ingredients')?.addEventListener('click', () => {
        if (sumDayNutrition(getDayPlan(state.plans, state.selected)).mealCount === 0) return;
        renderIngredientsSheet(state);
        openSheet(ingBackdrop, ingSheet);
    });

    ingBackdrop?.addEventListener('click', () => {
        closeSheet(ingBackdrop, ingSheet);
    });

    const closeCopy = () => closeSheet(copyBackdrop, copySheet);
    bindPlannerSheetDragClose(copySheet, closeCopy);
    copyBackdrop?.addEventListener('click', closeCopy);

    document.getElementById('planner-copy-day')?.addEventListener('click', () => {
        const srcKey = dateKey(state.selected);
        const srcPlan = state.plans[srcKey];
        if (!srcPlan || Object.keys(srcPlan).length === 0) {
            showPlannerToast('Ten dzień jest pusty — nie ma co kopiować.');
            return;
        }
        const copyList = document.getElementById('planner-copy-list');
        if (!copyList) return;
        const days = [];
        for (let i = -3; i <= 10; i++) {
            if (i === 0) continue;
            const d = addDays(state.selected, i);
            days.push(d);
        }
        copyList.innerHTML = days.map((d) => {
            const wd = WEEKDAYS_LONG[d.getDay()];
            const label = `${wd}, ${d.getDate()} ${MONTHS_SHORT[d.getMonth()]}`;
            const hasMeals = dayHasAnyMeal(state.plans, d);
            const badge = hasMeals ? '<span class="text-[10px] text-amber-600 font-semibold">ma posiłki</span>' : '';
            return `<button type="button" class="planner-copy-target w-full flex items-center justify-between gap-2 p-3 rounded-xl border border-gray-200 bg-gray-50/80 hover:border-gray-900 hover:bg-white transition-all text-left" data-target-ts="${d.getTime()}">
                <span class="text-[13px] font-semibold text-gray-900">${escapeHtml(label)}</span>
                ${badge}
            </button>`;
        }).join('');
        openSheet(copyBackdrop, copySheet);
    });

    document.getElementById('planner-copy-list')?.addEventListener('click', (e) => {
        const btn = e.target.closest('.planner-copy-target');
        if (!btn) return;
        const targetDate = new Date(Number(btn.getAttribute('data-target-ts')));
        const srcKey = dateKey(state.selected);
        const tgtKey = dateKey(targetDate);
        const srcPlan = state.plans[srcKey];
        if (!srcPlan) return;

        const copy = {};
        for (const [slotId, entries] of Object.entries(srcPlan)) {
            if (slotId === '_skipped') {
                copy._skipped = { ...entries };
                continue;
            }
            if (Array.isArray(entries)) {
                copy[slotId] = entries.map((e) => ({ ...e, id: newPlanEntryId() }));
            }
        }
        state.plans[tgtKey] = copy;
        closeCopy();
        persist();
        showPlannerToast('Plan skopiowany!');
    });

    ingSheet?.addEventListener('click', (e) => {
        const row = e.target.closest('.planner-ing-row');
        if (!row || !ingSheet.contains(row)) return;
        row.classList.toggle('ingredient-active');
    });

    document.getElementById('planner-ing-add-all')?.addEventListener('click', () => {
        const items = state._todayShortfalls || [];
        if (items.length === 0) return;
        const lines = items.map((it) => ({
            ingredientId: it.ingredientId,
            amount: it.shortfall,
            unit: it.pantryUnit,
            category: it.category,
            sourceNote: 'Braki z planu dnia',
        }));
        addOrMergeShoppingLines(lines);
        showPlannerToast(`Dodano ${lines.length} braków na listę zakupów.`);
        window.refreshShopping?.();
        closeSheet(ingBackdrop, ingSheet);
    });

    document.getElementById('planner-ing-add-btn')?.addEventListener('click', () => {
        const items = state._allForecastShortfalls || [];
        if (items.length === 0) return;
        const map = new Map();
        for (const it of items) {
            const key = it.ingredientId;
            if (map.has(key)) {
                const cur = map.get(key);
                cur.amount = Math.round((cur.amount + it.shortfall) * 10) / 10;
            } else {
                map.set(key, {
                    ingredientId: it.ingredientId,
                    amount: it.shortfall,
                    unit: it.pantryUnit,
                    category: it.category,
                    sourceNote: 'Braki z planu tygodnia',
                });
            }
        }
        const lines = [...map.values()];
        addOrMergeShoppingLines(lines);
        showPlannerToast(`Dodano ${lines.length} braków na listę zakupów.`);
        window.refreshShopping?.();
        closeSheet(ingBackdrop, ingSheet);
    });

    rerender();

    window.refreshPlanner = () => {
        state.plans = loadPlans();
        rerender();
    };

    bindCalendarSwipeGesture(state, rerender);

    requestAnimationFrame(() => {
        const ww = document.getElementById('calendar-week-wrap');
        const mw = document.getElementById('calendar-month-wrap');
        const t = 'max-height 300ms ease, opacity 200ms ease';
        if (ww) ww.style.transition = t;
        if (mw) mw.style.transition = t;
    });
}
