import { INGREDIENTS, CATEGORY_LABELS, PRODUCTS, ingredientHasProducts } from '../data/catalog.js?v=8';
import { PANTRY_STORAGE_KEY, PANTRY_STORAGE_KEY_V2, SHOPPING_STORAGE_KEY } from '../storageKeys.js';

export const KITCHEN_LIST_ID = 'kitchen';
export const MISC_LIST_ID = 'misc';

function newId(prefix) {
    if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
        return crypto.randomUUID();
    }
    return `${prefix}${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
}

/** @typedef {{ id: string, ingredientId: string, productId?: string, name: string, amount: number, unit: string, category: string, checked: boolean, sourceNote?: string }} KitchenShoppingItem */
/** @typedef {{ id: string, text: string, note?: string, checked: boolean }} FreeformShoppingItem */
/** @typedef {{ id: string, name: string, type: 'kitchen'|'freeform', items: (KitchenShoppingItem|FreeformShoppingItem)[] }} ShoppingListDef */

/**
 * @typedef {{
 *   lists: ShoppingListDef[],
 *   activeListId: string
 * }} ShoppingState
 */

function defaultShoppingState() {
    return {
        lists: [
            { id: KITCHEN_LIST_ID, name: 'Kuchnia', type: 'kitchen', items: [] },
            { id: MISC_LIST_ID, name: 'Inne zakupy', type: 'freeform', items: [] },
        ],
        activeListId: KITCHEN_LIST_ID,
    };
}

/** @param {unknown} x */
function normalizeKitchenItem(x) {
    if (!x || typeof x !== 'object') return null;
    const o = /** @type {Record<string, unknown>} */ (x);
    if (!o.ingredientId || !Number.isFinite(Number(o.amount))) return null;
    const id = String(o.id && String(o.id).length ? o.id : newId('s'));
    const ingId = String(o.ingredientId);
    const item = {
        id,
        ingredientId: ingId,
        name: String(o.name || INGREDIENTS[ingId]?.name || ingId),
        amount: Math.round(Number(o.amount) * 100) / 100,
        unit: String(o.unit || ''),
        category: String(o.category || INGREDIENTS[ingId]?.category || 'inne'),
        checked: Boolean(o.checked),
        sourceNote: o.sourceNote ? String(o.sourceNote) : undefined,
    };
    if (o.productId && typeof o.productId === 'string' && PRODUCTS[o.productId]) {
        item.productId = o.productId;
    }
    return item;
}

/** @param {unknown} x */
function normalizeFreeformItem(x) {
    if (!x || typeof x !== 'object') return null;
    const o = /** @type {Record<string, unknown>} */ (x);
    if (!o.text || !String(o.text).trim()) return null;
    return {
        id: String(o.id && String(o.id).length ? o.id : newId('f')),
        text: String(o.text).trim(),
        note: o.note ? String(o.note) : undefined,
        checked: Boolean(o.checked),
    };
}

/** @returns {ShoppingState} */
function normalizeShoppingState(raw) {
    const base = defaultShoppingState();
    if (!raw || typeof raw !== 'object') return base;
    const p = /** @type {Record<string, unknown>} */ (raw);

    if (Array.isArray(p)) {
        const items = p.map(normalizeKitchenItem).filter(Boolean);
        const kitchen = base.lists.find((l) => l.id === KITCHEN_LIST_ID);
        if (kitchen && kitchen.type === 'kitchen') {
            kitchen.items = /** @type {KitchenShoppingItem[]} */ (items);
        }
        return base;
    }

    if (!Array.isArray(p.lists)) return base;

    /** @type {ShoppingListDef[]} */
    const lists = [];
    for (const L of p.lists) {
        if (!L || typeof L !== 'object') continue;
        const row = /** @type {Record<string, unknown>} */ (L);
        const id = String(row.id || '');
        const name = String(row.name || 'Lista');
        let type = row.type === 'freeform' ? 'freeform' : 'kitchen';
        if (id === KITCHEN_LIST_ID) type = 'kitchen';
        if (id === MISC_LIST_ID) type = 'freeform';
        if (!id) continue;
        if (type === 'kitchen') {
            const items = Array.isArray(row.items)
                ? row.items.map(normalizeKitchenItem).filter(Boolean)
                : [];
            lists.push({ id, name, type: 'kitchen', items: /** @type {KitchenShoppingItem[]} */ (items) });
        } else {
            const items = Array.isArray(row.items)
                ? row.items.map(normalizeFreeformItem).filter(Boolean)
                : [];
            lists.push({ id, name, type: 'freeform', items: /** @type {FreeformShoppingItem[]} */ (items) });
        }
    }

    if (lists.length === 0) return base;

    const hasKitchen = lists.some((l) => l.id === KITCHEN_LIST_ID);
    if (!hasKitchen) {
        lists.unshift(/** @type {ShoppingListDef} */ (base.lists[0]));
    }
    const hasMisc = lists.some((l) => l.id === MISC_LIST_ID);
    if (!hasMisc) {
        lists.push(/** @type {ShoppingListDef} */ (base.lists[1]));
    }

    let activeListId = String(p.activeListId || KITCHEN_LIST_ID);
    if (!lists.some((l) => l.id === activeListId)) activeListId = lists[0].id;

    return { lists, activeListId };
}

export function loadShoppingState() {
    try {
        const raw = localStorage.getItem(SHOPPING_STORAGE_KEY);
        if (!raw) return defaultShoppingState();
        const parsed = JSON.parse(raw);
        return normalizeShoppingState(parsed);
    } catch {
        return defaultShoppingState();
    }
}

export function saveShoppingState(state) {
    try {
        localStorage.setItem(SHOPPING_STORAGE_KEY, JSON.stringify(state));
    } catch { /* ignore */ }
}

export function getListSummaries() {
    const { lists, activeListId } = loadShoppingState();
    return {
        lists: lists.map((l) => ({
            id: l.id,
            name: l.name,
            type: l.type,
            openCount: l.items.filter((i) => !i.checked).length,
        })),
        activeListId,
    };
}

export function setActiveListId(listId) {
    const s = loadShoppingState();
    if (!s.lists.some((l) => l.id === listId)) return s;
    s.activeListId = listId;
    saveShoppingState(s);
    return s;
}

export function getListById(listId) {
    return loadShoppingState().lists.find((l) => l.id === listId) ?? null;
}

export function getActiveList() {
    const s = loadShoppingState();
    return s.lists.find((l) => l.id === s.activeListId) ?? s.lists[0];
}

/**
 * @param {string} name
 * @returns {string} new list id
 */
export function addFreeformList(name) {
    const s = loadShoppingState();
    const id = newId('list');
    s.lists.push({
        id,
        name: name.trim() || 'Nowa lista',
        type: 'freeform',
        items: [],
    });
    s.activeListId = id;
    saveShoppingState(s);
    return id;
}

/**
 * @param {string} listId
 * @returns {boolean}
 */
export function deleteList(listId) {
    if (listId === KITCHEN_LIST_ID) return false;
    const s = loadShoppingState();
    const idx = s.lists.findIndex((l) => l.id === listId);
    if (idx < 0) return false;
    s.lists.splice(idx, 1);
    if (s.activeListId === listId) {
        s.activeListId = s.lists[0]?.id || KITCHEN_LIST_ID;
    }
    saveShoppingState(s);
    return true;
}

/**
 * Dodaje linie tylko do listy kuchennej (składniki z katalogu).
 * @param {{ ingredientId: string, amount: number, unit: string, name?: string, category?: string, sourceNote?: string }[]} lines
 * @param {string} [listId]
 */
export function addOrMergeShoppingLines(lines, listId = KITCHEN_LIST_ID) {
    const s = loadShoppingState();
    const list = s.lists.find((l) => l.id === listId && l.type === 'kitchen');
    if (!list || list.type !== 'kitchen') return s;

    const items = /** @type {KitchenShoppingItem[]} */ (list.items);
    const open = items.filter((x) => !x.checked);
    const closed = items.filter((x) => x.checked);

    for (const L of lines) {
        const def = INGREDIENTS[L.ingredientId];
        const name = L.name || def?.name || L.ingredientId;
        const category = L.category || def?.category || 'inne';
        const productId = L.productId && PRODUCTS[L.productId] ? L.productId : undefined;
        const productName = productId ? PRODUCTS[productId].name : null;
        const displayName = productName || name;
        const idx = open.findIndex(
            (x) => x.ingredientId === L.ingredientId && x.unit === L.unit && (x.productId || '') === (productId || ''),
        );
        if (idx >= 0) {
            open[idx].amount = Math.round((open[idx].amount + L.amount) * 100) / 100;
            if (L.sourceNote && !open[idx].sourceNote) open[idx].sourceNote = L.sourceNote;
        } else {
            const item = {
                id: newId('s'),
                ingredientId: L.ingredientId,
                name: displayName,
                amount: Math.round(L.amount * 100) / 100,
                unit: L.unit,
                category,
                checked: false,
                sourceNote: L.sourceNote,
            };
            if (productId) item.productId = productId;
            open.push(item);
        }
    }

    list.items = [...open, ...closed];
    saveShoppingState(s);
    return s;
}

/**
 * Na listę kuchenną ze spiżarni (amount w pantryUnit: g, ml lub szt.).
 * @param {string} [sourceNote] — nadpisuje domyślne „Ze spiżarni”
 */
export function addIngredientToKitchenList(ingredientId, amount = 1, sourceNote) {
    const def = INGREDIENTS[ingredientId];
    if (!def) return;
    const unit = displayUnit(def.pantryUnit);
    addOrMergeShoppingLines([{
        ingredientId,
        amount,
        unit,
        name: def.name,
        category: def.category,
        sourceNote: sourceNote ?? 'Ze spiżarni',
    }], KITCHEN_LIST_ID);
}

/**
 * @param {string} listId
 * @param {string} text
 * @param {string} [note]
 */
export function addFreeformLine(listId, text, note) {
    const t = text.trim();
    if (!t) return;
    const s = loadShoppingState();
    const list = s.lists.find((l) => l.id === listId && l.type === 'freeform');
    if (!list) return;
    const items = /** @type {FreeformShoppingItem[]} */ (list.items);
    items.push({
        id: newId('f'),
        text: t,
        note: note?.trim() || undefined,
        checked: false,
    });
    saveShoppingState(s);
}

export function toggleItemInList(listId, itemId) {
    const s = loadShoppingState();
    const list = s.lists.find((l) => l.id === listId);
    if (!list) return s;
    const it = list.items.find((i) => i.id === itemId);
    if (it) it.checked = !it.checked;
    saveShoppingState(s);
    return s;
}

export function updateKitchenItemAmount(listId, itemId, newAmount) {
    const s = loadShoppingState();
    const list = s.lists.find((l) => l.id === listId && l.type === 'kitchen');
    if (!list) return s;
    const it = /** @type {KitchenShoppingItem[]} */ (list.items).find((i) => i.id === itemId);
    if (!it) return s;
    it.amount = Math.max(0, Math.round(newAmount * 100) / 100);
    if (it.amount <= 0) {
        list.items = list.items.filter((i) => i.id !== itemId);
    }
    saveShoppingState(s);
    return s;
}

export function removeItemFromList(listId, itemId) {
    const s = loadShoppingState();
    const list = s.lists.find((l) => l.id === listId);
    if (!list) return s;
    list.items = list.items.filter((i) => i.id !== itemId);
    saveShoppingState(s);
    return s;
}

export function clearCheckedInList(listId) {
    const s = loadShoppingState();
    const list = s.lists.find((l) => l.id === listId);
    if (!list) return s;
    list.items = list.items.filter((i) => !i.checked);
    saveShoppingState(s);
    return s;
}

export function computeShortfalls(needLines, pantry = loadPantry()) {
    const short = [];
    for (const L of needLines) {
        const def = INGREDIENTS[L.ingredientId];
        if (!def) continue;
        const u = normalizeUnitForPantry(L.unit, def.pantryUnit);
        if (u === null) {
            short.push({ ...L, pantryQty: 0, shortfall: L.amount, unitMismatch: true });
            continue;
        }
        const have = getPantryTotal(L.ingredientId, pantry);
        const miss = Math.max(0, Math.round((L.amount - have) * 100) / 100);
        if (miss > 0) {
            short.push({
                ...L,
                unit: displayUnit(def.pantryUnit),
                pantryQty: have,
                shortfall: miss,
                unitMismatch: false,
            });
        }
    }
    return short;
}

function displayUnit(pantryUnit) {
    if (pantryUnit === 'szt') return 'szt.';
    return pantryUnit;
}

function normalizeUnitForPantry(recipeUnit, pantryUnit) {
    const r = String(recipeUnit).trim().toLowerCase().replace(/\.$/, '');
    if (pantryUnit === 'g' && (r === 'g' || r === 'gram')) return 'g';
    if (pantryUnit === 'ml' && (r === 'ml' || r === 'mililitr')) return 'ml';
    if (pantryUnit === 'szt' && (r === 'szt' || r === 'sztuka' || r === 'kromki' || r === 'kromka')) return 'szt';
    return null;
}

export function categoryLabel(cat) {
    return CATEGORY_LABELS[cat] || CATEGORY_LABELS.inne;
}

/* ══════════════════════════════════════════════════════════════════════
 * Pantry v2 — hybrydowy format.
 *
 * Wartość dla składnika może być:
 *   number           — składnik bez produktów (generyczny)
 *   { _total, items: [{productId, qty}] } — składnik z produktami
 *
 * Brak pojęcia "generic" — jeśli składnik ma produkty, każda ilość
 * musi być przypisana do konkretnego produktu.
 * ══════════════════════════════════════════════════════════════════════ */

/** @typedef {{ productId: string, qty: number }} PantryProductItem */
/** @typedef {{ _total: number, items: PantryProductItem[] }} PantryProductEntry */
/** @typedef {Record<string, number | PantryProductEntry>} PantryV2 */

function recalcTotal(entry) {
    entry._total = Math.round(entry.items.reduce((s, i) => s + i.qty, 0) * 1000) / 1000;
    return entry;
}

function normalizePantryEntry(ingredientId, val) {
    if (val && typeof val === 'object' && !Array.isArray(val)) {
        const items = Array.isArray(val.items)
            ? val.items
                  .filter(i => i && typeof i.productId === 'string' && PRODUCTS[i.productId] && Number.isFinite(Number(i.qty)) && Number(i.qty) > 0)
                  .map(i => ({ productId: i.productId, qty: Math.round(Number(i.qty) * 1000) / 1000 }))
            : [];
        // Migrate generic stock → first product
        const generic = Math.max(0, Number(val.generic) || 0);
        if (generic > 0 && ingredientHasProducts(ingredientId)) {
            const products = getProductsForIngredient(ingredientId);
            if (products.length > 0) {
                const firstPid = products[0].id;
                const existing = items.find(i => i.productId === firstPid);
                if (existing) existing.qty = Math.round((existing.qty + generic) * 1000) / 1000;
                else items.push({ productId: firstPid, qty: Math.round(generic * 1000) / 1000 });
            }
        }
        return recalcTotal({ items, _total: 0 });
    }
    const n = Number(val);
    if (!Number.isFinite(n) || n < 0) return null;
    if (ingredientHasProducts(ingredientId)) {
        // Migrate number → first product
        if (n > 0) {
            const products = getProductsForIngredient(ingredientId);
            if (products.length > 0) {
                return recalcTotal({ items: [{ productId: products[0].id, qty: n }], _total: 0 });
            }
        }
        return recalcTotal({ items: [], _total: 0 });
    }
    return n;
}

function migrateV1toV2() {
    try {
        const raw = localStorage.getItem(PANTRY_STORAGE_KEY);
        if (!raw) return {};
        const p = JSON.parse(raw);
        if (typeof p !== 'object' || p === null) return {};
        const out = {};
        for (const [k, v] of Object.entries(p)) {
            const norm = normalizePantryEntry(k, v);
            if (norm !== null) out[k] = norm;
        }
        return out;
    } catch {
        return {};
    }
}

/** @returns {PantryV2} */
export function loadPantry() {
    try {
        const rawV2 = localStorage.getItem(PANTRY_STORAGE_KEY_V2);
        if (rawV2) {
            const p = JSON.parse(rawV2);
            if (typeof p !== 'object' || p === null) return {};
            const out = {};
            for (const [k, v] of Object.entries(p)) {
                const norm = normalizePantryEntry(k, v);
                if (norm !== null) out[k] = norm;
            }
            return out;
        }
        const migrated = migrateV1toV2();
        if (Object.keys(migrated).length > 0) {
            savePantry(migrated);
        }
        return migrated;
    } catch {
        return {};
    }
}

/** @param {PantryV2} pantry */
export function savePantry(pantry) {
    try {
        localStorage.setItem(PANTRY_STORAGE_KEY_V2, JSON.stringify(pantry));
    } catch { /* ignore */ }
}

/** Łączna ilość składnika (generyczna + produkty). */
export function getPantryTotal(ingredientId, pantry) {
    const val = pantry[ingredientId];
    if (val == null) return 0;
    if (typeof val === 'number') return val;
    return val._total || 0;
}

/** Lista produktów w spiżarni dla danego składnika. */
export function getPantryProducts(ingredientId, pantry) {
    const val = pantry[ingredientId];
    if (!val || typeof val === 'number') return [];
    return val.items || [];
}

/** @deprecated No generic stock for ingredients with products. Returns 0 for those. */
export function getPantryGeneric(ingredientId, pantry) {
    const val = pantry[ingredientId];
    if (typeof val === 'number') return val;
    return 0;
}

/** Ustaw ilość składnika BEZ produktów. Dla składników z produktami użyj setPantryProductQty. */
export function setPantryQty(ingredientId, qty) {
    const pantry = loadPantry();
    if (qty <= 0 || !Number.isFinite(qty)) delete pantry[ingredientId];
    else pantry[ingredientId] = Math.round(qty * 1000) / 1000;
    savePantry(pantry);
    return pantry;
}

/** Ustaw ilość konkretnego produktu w spiżarni. */
export function setPantryProductQty(ingredientId, productId, qty) {
    const pantry = loadPantry();
    let val = pantry[ingredientId];
    if (!val || typeof val === 'number') {
        val = { items: [], _total: 0 };
        pantry[ingredientId] = val;
    }
    const idx = val.items.findIndex(i => i.productId === productId);
    const q = Math.max(0, Math.round(qty * 1000) / 1000);
    if (q > 0) {
        if (idx >= 0) val.items[idx].qty = q;
        else val.items.push({ productId, qty: q });
    } else {
        if (idx >= 0) val.items.splice(idx, 1);
    }
    recalcTotal(val);
    if (val._total <= 0) delete pantry[ingredientId];
    savePantry(pantry);
    return pantry;
}

/** Kupione ze listy kuchennej → spiżarnia (zawsze ta lista, niezależnie od aktywnej zakładki). */
export function applyCheckedKitchenListToPantry() {
    const s = loadShoppingState();
    const kitchen = s.lists.find((l) => l.id === KITCHEN_LIST_ID && l.type === 'kitchen');
    if (!kitchen) return { pantry: loadPantry(), state: s };

    const pantry = loadPantry();
    const items = /** @type {KitchenShoppingItem[]} */ (kitchen.items);

    for (const it of items) {
        if (!it.checked) continue;
        const def = INGREDIENTS[it.ingredientId];
        if (!def) continue;
        if (normalizeUnitForPantry(it.unit, def.pantryUnit) === null) continue;

        if (it.productId && PRODUCTS[it.productId]) {
            // Add to specific product
            let val = pantry[it.ingredientId];
            if (!val || typeof val === 'number') {
                val = { items: [], _total: 0 };
                pantry[it.ingredientId] = val;
            }
            const idx = val.items.findIndex(i => i.productId === it.productId);
            if (idx >= 0) val.items[idx].qty = Math.round((val.items[idx].qty + it.amount) * 1000) / 1000;
            else val.items.push({ productId: it.productId, qty: Math.round(it.amount * 1000) / 1000 });
            recalcTotal(val);
        } else {
            // Generic ingredient (no products)
            const cur = typeof pantry[it.ingredientId] === 'number' ? pantry[it.ingredientId] : 0;
            pantry[it.ingredientId] = Math.round((cur + it.amount) * 1000) / 1000;
        }
    }

    kitchen.items = items.filter((i) => !i.checked);
    savePantry(pantry);
    saveShoppingState(s);
    return { pantry, state: s };
}
