import { INGREDIENTS, RECIPES } from '../data/catalog.js';
import { MEAL_SLOTS } from '../planner/mealSlots.js';
import { addDays } from './dateUtils.js';
import { getDayPlan } from './planStore.js';

export function dayHasAnyMeal(plans, d) {
    const p = getDayPlan(plans, d);
    const skipped = p._skipped || {};
    return MEAL_SLOTS.some((s) => {
        if (skipped[s.id]) return false;
        const arr = p[s.id];
        return Array.isArray(arr) && arr.length > 0;
    });
}

function hasCustomizations(entry) {
    return (entry.excludedIngredients?.length > 0) ||
        (entry.amountOverrides && Object.keys(entry.amountOverrides).length > 0) ||
        (entry.addedIngredients?.length > 0) ||
        (entry.substitutions && Object.keys(entry.substitutions).length > 0);
}

function nutritionForAmountRaw(ingredientId, amount, unit) {
    const def = INGREDIENTS[ingredientId];
    if (!def?.nutritionPer100g) return null;
    let g = amount;
    if ((unit === 'szt.' || unit === 'szt') && def.weightPerPiece) g = amount * def.weightPerPiece;
    const f = g / 100;
    return {
        kcal: def.nutritionPer100g.kcal * f,
        protein: def.nutritionPer100g.protein * f,
        fat: def.nutritionPer100g.fat * f,
        carbs: def.nutritionPer100g.carbs * f,
    };
}

export function computeEntryNutrition(entry) {
    if (!entry?.recipeId) return { kcal: 0, protein: 0, fat: 0, carbs: 0 };
    const r = RECIPES[entry.recipeId];
    if (!r) return { kcal: 0, protein: 0, fat: 0, carbs: 0 };
    const s = Math.max(1, Number(entry.servings) || 1);

    if (!hasCustomizations(entry)) {
        return {
            kcal: Math.round(r.nutritionPerServing.kcal * s),
            protein: Math.round(r.nutritionPerServing.protein * s * 10) / 10,
            fat: Math.round(r.nutritionPerServing.fat * s * 10) / 10,
            carbs: Math.round(r.nutritionPerServing.carbs * s * 10) / 10,
        };
    }

    const excluded = new Set(entry.excludedIngredients || []);
    const overrides = entry.amountOverrides || {};
    const subs = entry.substitutions || {};
    let kcal = 0, protein = 0, fat = 0, carbs = 0;

    for (const ing of r.ingredients) {
        if (excluded.has(ing.ingredientId)) continue;
        const eid = subs[ing.ingredientId] || ing.ingredientId;
        const base = overrides[ing.ingredientId] ?? ing.amount;
        const n = nutritionForAmountRaw(eid, base * s, ing.unit);
        if (n) { kcal += n.kcal; protein += n.protein; fat += n.fat; carbs += n.carbs; }
    }
    for (const a of (entry.addedIngredients || [])) {
        const n = nutritionForAmountRaw(a.ingredientId, a.amount * s, a.unit);
        if (n) { kcal += n.kcal; protein += n.protein; fat += n.fat; carbs += n.carbs; }
    }

    return {
        kcal: Math.round(kcal),
        protein: Math.round(protein * 10) / 10,
        fat: Math.round(fat * 10) / 10,
        carbs: Math.round(carbs * 10) / 10,
    };
}

export function sumDayNutrition(dayPlan) {
    let kcal = 0, protein = 0, fat = 0, carbs = 0, mealCount = 0;
    const skipped = dayPlan._skipped || {};
    MEAL_SLOTS.forEach((slot) => {
        if (skipped[slot.id]) return;
        const entries = dayPlan[slot.id];
        if (!Array.isArray(entries)) return;
        entries.forEach((entry) => {
            if (!entry?.recipeId || !RECIPES[entry.recipeId]) return;
            mealCount += 1;
            const n = computeEntryNutrition(entry);
            kcal += n.kcal; protein += n.protein; fat += n.fat; carbs += n.carbs;
        });
    });
    return {
        kcal: Math.round(kcal),
        protein: Math.round(protein * 10) / 10,
        fat: Math.round(fat * 10) / 10,
        carbs: Math.round(carbs * 10) / 10,
        mealCount,
    };
}

function resolveLine(ing, scaledAmount) {
    const def = INGREDIENTS[ing.ingredientId];
    return {
        ingredientId: ing.ingredientId,
        name: def?.name ?? ing.ingredientId,
        category: def?.category ?? 'inne',
        amount: scaledAmount,
        unit: ing.unit,
    };
}

/** Płaska lista składników z jednego dnia (wszystkie pory). */
export function flattenDayIngredientLines(dayPlan) {
    /** @type {ReturnType<typeof resolveLine>[]} */
    const out = [];
    const skipped = dayPlan._skipped || {};
    MEAL_SLOTS.forEach((slot) => {
        if (skipped[slot.id]) return;
        const entries = dayPlan[slot.id];
        if (!Array.isArray(entries)) return;
        entries.forEach((entry) => {
            if (!entry || !entry.recipeId) return;
            const r = RECIPES[entry.recipeId];
            if (!r || !Array.isArray(r.ingredients)) return;
            const serv = Math.max(1, Number(entry.servings) || 1);
            const excluded = new Set(entry.excludedIngredients || []);
            const overrides = entry.amountOverrides || {};
            const subs = entry.substitutions || {};
            r.ingredients.forEach((ing) => {
                if (excluded.has(ing.ingredientId)) return;
                const effectiveId = subs[ing.ingredientId] || ing.ingredientId;
                const base = overrides[ing.ingredientId] ?? ing.amount;
                const scaled = Math.round(base * serv * 10) / 10;
                out.push(resolveLine({ ingredientId: effectiveId, unit: ing.unit }, scaled));
            });
            for (const a of (entry.addedIngredients || [])) {
                const scaled = Math.round(a.amount * serv * 10) / 10;
                out.push(resolveLine(a, scaled));
            }
        });
    });
    return out;
}

/** Sumuje po (ingredientId + unit) — ta sama jednostka jak w przepisie. */
export function mergeIngredientLines(lines) {
    const m = new Map();
    for (const L of lines) {
        const key = `${L.ingredientId}\t${L.unit}`;
        const cur = m.get(key);
        if (!cur) {
            m.set(key, { ...L });
        } else {
            cur.amount = Math.round((cur.amount + L.amount) * 10) / 10;
        }
    }
    return [...m.values()].sort((a, b) => {
        const c = a.category.localeCompare(b.category);
        return c !== 0 ? c : a.name.localeCompare(b.name);
    });
}

/**
 * Zapotrzebowanie składników od weekStart (włącznie) przez 7 dni.
 * @param {Record<string, unknown>} plans
 * @param {Date} weekStart
 */
export function aggregateWeekIngredientNeed(plans, weekStart) {
    const all = [];
    for (let i = 0; i < 7; i++) {
        const day = addDays(weekStart, i);
        const dayPlan = getDayPlan(plans, day);
        all.push(...flattenDayIngredientLines(dayPlan));
    }
    return mergeIngredientLines(all);
}

/**
 * Jedna grupa na porę dnia: nagłówek pory raz, potem bloki przepisów ze składnikami.
 */
export function aggregateDayIngredientsBySlot(dayPlan) {
    /** @type {{ mealLabel: string, recipes: { recipeTitle: string, items: { ingredientId: string, name: string, amount: number, unit: string, category: string }[] }[] }[]} */
    const blocks = [];
    const skipped = dayPlan._skipped || {};
    MEAL_SLOTS.forEach((slot) => {
        if (skipped[slot.id]) return;
        const entries = dayPlan[slot.id];
        if (!Array.isArray(entries) || entries.length === 0) return;
        const recipes = [];
        entries.forEach((entry) => {
            if (!entry || !entry.recipeId) return;
            const r = RECIPES[entry.recipeId];
            if (!r) return;
            const s = Math.max(1, Number(entry.servings) || 1);
            const excluded = new Set(entry.excludedIngredients || []);
            const overrides = entry.amountOverrides || {};
            const subs = entry.substitutions || {};
            const items = [];
            r.ingredients.forEach((ing) => {
                if (excluded.has(ing.ingredientId)) return;
                const effectiveId = subs[ing.ingredientId] || ing.ingredientId;
                const base = overrides[ing.ingredientId] ?? ing.amount;
                const def = INGREDIENTS[effectiveId];
                items.push({
                    ingredientId: effectiveId,
                    name: def?.name ?? effectiveId,
                    category: def?.category ?? 'inne',
                    amount: Math.round(base * s * 10) / 10,
                    unit: ing.unit,
                });
            });
            for (const a of (entry.addedIngredients || [])) {
                const def = INGREDIENTS[a.ingredientId];
                items.push({
                    ingredientId: a.ingredientId,
                    name: def?.name ?? a.ingredientId,
                    category: def?.category ?? 'inne',
                    amount: Math.round(a.amount * s * 10) / 10,
                    unit: a.unit,
                });
            }
            recipes.push({ recipeTitle: r.title, items });
        });
        if (recipes.length > 0) {
            blocks.push({ mealLabel: slot.label, recipes });
        }
    });
    return blocks;
}

export function countDayShortfalls(dayPlan, pantry) {
    const lines = mergeIngredientLines(flattenDayIngredientLines(dayPlan));
    let count = 0;
    for (const line of lines) {
        if ((Number(pantry[line.ingredientId]) || 0) < line.amount) count++;
    }
    return count;
}

/**
 * Kumulatywna prognoza zużycia spiżarni: od startDate przez lookAheadDays dni.
 * Zwraca tablicę dni (tylko te z posiłkami), każdy z listą składników i informacją
 * ile jest w spiżarni (po odjęciu zużycia z poprzednich dni) i ile brakuje.
 */
export function computeFullForecast(plans, pantry, startDate, lookAheadDays = 8) {
    const running = { ...pantry };
    const days = [];

    for (let i = 0; i < lookAheadDays; i++) {
        const day = addDays(startDate, i);
        const dayPlan = getDayPlan(plans, day);
        const lines = mergeIngredientLines(flattenDayIngredientLines(dayPlan));
        if (lines.length === 0) continue;

        const items = lines.map((line) => {
            const def = INGREDIENTS[line.ingredientId];
            const have = Math.round((Number(running[line.ingredientId]) || 0) * 10) / 10;
            const miss = Math.max(0, Math.round((line.amount - have) * 10) / 10);
            return {
                ...line,
                pantryQty: have,
                shortfall: miss,
                enough: miss <= 0,
                pantryUnit: def
                    ? def.pantryUnit === 'szt' ? 'szt.' : def.pantryUnit
                    : line.unit,
            };
        });

        for (const line of lines) {
            const have = Number(running[line.ingredientId]) || 0;
            running[line.ingredientId] = Math.max(0, Math.round((have - line.amount) * 10) / 10);
        }

        days.push({ date: day, dayIndex: i, items, hasShortfall: items.some((it) => !it.enough) });
    }

    return days;
}
