// pantry-app.jsx — main app composition + tweaks + frame switcher

const { useState: useStateA, useEffect: useEffectA, useMemo: useMemoA } = React;

// ─────────────────────────────────────────────────────────────
// Tweak defaults (editable on disk)
// ─────────────────────────────────────────────────────────────
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "frame": "iphone",
  "palette": "sage",
  "iconStyle": "emoji",
  "density": "cards",
  "recipeStyle": "standard",
  "dashStyle": "hero",
  "lang": "fr",
  "showOnboarding": true,
  "autoAddMissing": true
}/*EDITMODE-END*/;

// ─────────────────────────────────────────────────────────────
// Pantry app content — the actual screen
// ─────────────────────────────────────────────────────────────
// ─────────────────────────────────────────────────────────────
// localStorage persistence helpers
// ─────────────────────────────────────────────────────────────
const LS_KEY = 'pantrywise.v1';
const LS_ONBOARDED = 'pantrywise.onboarded';
function loadPersisted() {
  try {
    const raw = localStorage.getItem(LS_KEY);
    if (!raw) return null;
    const data = JSON.parse(raw);
    if (data.favorites && Array.isArray(data.favorites)) data.favorites = new Set(data.favorites);
    if (data.mealPlan && window.migrateMealPlan) data.mealPlan = window.migrateMealPlan(data.mealPlan);
    return data;
  } catch (e) { return null; }
}
function savePersisted(data) {
  try {
    const out = { ...data };
    if (data.favorites instanceof Set) out.favorites = [...data.favorites];
    localStorage.setItem(LS_KEY, JSON.stringify(out));
  } catch (e) {}
}
function hasOnboarded() {
  try { return localStorage.getItem(LS_ONBOARDED) === '1'; } catch (e) { return false; }
}
function markOnboarded() {
  try { localStorage.setItem(LS_ONBOARDED, '1'); } catch (e) {}
}

function PantryAppContent({ palette, lang, iconStyle, density, recipeStyle, dashStyle, web, topInset, onChangeLang, showOnboardingTweak, autoAddMissingTweak, setTweak, tweaks, authUser, onSignOut, onShowLogin, authStatus }) {
  const persisted = useMemoA(() => loadPersisted(), []);
  // Authed users start blank — seed demo data is only for guest/design-preview mode.
  // (If they already have synced data, usePantrySync will pull it in shortly.)
  const blankStart = !!authUser && !persisted;
  const _normInv = (l) => (window.normalizeInventory ? window.normalizeInventory(l) : l);
  const [inventory, setInventory] = useStateA(blankStart ? [] : _normInv(persisted?.inventory || DEFAULT_INVENTORY));
  const [grocery, setGrocery] = useStateA(blankStart ? [] : (persisted?.grocery || DEFAULT_GROCERY));
  const [mealPlan, setMealPlan] = useStateA(blankStart ? [] : (persisted?.mealPlan || MEAL_PLAN));
  const [leftovers, setLeftovers] = useStateA(blankStart ? [] : (persisted?.leftovers || DEFAULT_LEFTOVERS));
  const [favorites, setFavorites] = useStateA(persisted?.favorites instanceof Set ? persisted.favorites : new Set(blankStart ? [] : ['r1', 'r4']));
  const [customRecipes, setCustomRecipes] = useStateA(persisted?.customRecipes || []);
  // Real cooking log → drives honest stats (no hardcoded "$47 saved").
  // Each entry: { ts, rescued, savedCents } recorded when the user cooks.
  const [cookLog, setCookLog] = useStateA(persisted?.cookLog || []);
  // Household: when authed, default to just the current user. When unauthed (guest /
  // design preview), use the demo 3-person family so the UI looks populated.
  const [household, setHousehold] = useStateA(() => {
    if (persisted?.household) return persisted.household;
    if (authUser) {
      return [{
        id: authUser.id,
        name: authUser.name,
        tone: '#4F8267',
        initial: (authUser.name || '?').charAt(0).toUpperCase(),
      }];
    }
    return HOUSEHOLD;
  });
  const [privacyPrefs, setPrivacyPrefs] = useStateA(persisted?.privacyPrefs || { shareBreakfast: false, shareLunch: false, shareDinner: true });

  // Auto-save everything to localStorage on any change
  useEffectA(() => {
    savePersisted({ inventory, grocery, mealPlan, leftovers, favorites, customRecipes, household, privacyPrefs, cookLog });
  }, [inventory, grocery, mealPlan, leftovers, favorites, customRecipes, household, privacyPrefs, cookLog]);
  // Re-derive expiry countdowns each time the app opens (so freshness advances
  // with real days instead of being frozen at add-time).
  useEffectA(() => {
    if (window.normalizeInventory) setInventory(prev => window.normalizeInventory(prev));
  }, []);
  const [mealEditTarget, setMealEditTarget] = useStateA(null);  // {day, slot, meal?}
  const [editRecipeTarget, setEditRecipeTarget] = useStateA(null); // custom recipe being edited
  const [editItemTarget, setEditItemTarget] = useStateA(null);
  const [moveItemTarget, setMoveItemTarget] = useStateA(null);
  const [usedSomeTarget, setUsedSomeTarget] = useStateA(null);
  const [sheet, setSheet] = useStateA(null);
  const [picked, setPicked] = useStateA(null);
  const [selectedRecipe, setSelectedRecipe] = useStateA(null);
  const [itemMenu, setItemMenu] = useStateA(null);     // inventory item action menu target
  const [buyTarget, setBuyTarget] = useStateA(null);   // grocery item being confirmed
  const [toast, setToast] = useStateA(null);
  const [activeTab, setActiveTab] = useStateA('home');
  // Onboarding: real localStorage flag in production (PWA). Tweak toggle is for design mode preview only.
  // - Authed users skip onboarding (they already did it elsewhere, OR sync will populate their inventory).
  // - In standalone/mobile PWA: showOnboarding is true on first visit only (no LS_ONBOARDED flag).
  // - In design mode (tweaks panel visible): the Onboarding tweak controls it for previewing the flow.
  const [showOnboarding, setShowOnboarding] = useStateA(() => {
    if (authUser) return false;
    if (hasOnboarded()) return false;
    return showOnboardingTweak !== false;
  });
  const [autoAdd, setAutoAdd] = useStateA(autoAddMissingTweak);
  useEffectA(() => {
    if (authUser) { setShowOnboarding(false); markOnboarded(); return; }
    // Only let the tweak override if user hasn't gone through onboarding yet
    if (!hasOnboarded()) setShowOnboarding(showOnboardingTweak !== false);
  }, [showOnboardingTweak, authUser]);
  useEffectA(() => { setAutoAdd(autoAddMissingTweak); }, [autoAddMissingTweak]);

  // Sync with backend (only when authenticated). Pulls on mount, debounced push on change.
  const sync = (typeof window !== 'undefined' && window.usePantrySync)
    ? window.usePantrySync({
        enabled: !!authUser, user: authUser,
        inventory, setInventory, grocery, setGrocery,
        mealPlan, setMealPlan, leftovers, setLeftovers,
        favorites, setFavorites, customRecipes, setCustomRecipes,
        privacyPrefs, setPrivacyPrefs,
      })
    : null;

  // sort inventory by urgency for dashboard sections
  const sortedInv = useMemoA(() => {
    return [...inventory].sort((a, b) => a.daysLeft - b.daysLeft);
  }, [inventory]);

  const expiringItems = sortedInv.filter(i => i.daysLeft <= 5);
  // Pick the suggested recipe based on inventory
  const suggestedRecipe = useMemoA(() => {
    // r1 (avocado toast) wins if avocado in inventory; else r2 (pasta) if pasta+tomatoes; else first
    if (inventory.find(i => i.id === 's1')) return RECIPES[0];
    return RECIPES[1];
  }, [inventory]);

  // ─── handlers ───
  const openAdd = () => setSheet('add-method');
  const pickMethod = (m) => {
    if (m === 'quicklog') { setSheet('quick-log'); return; }
    setSheet('add-' + m);
  };
  const pickSuggestion = (s) => { setPicked(s); setSheet('add-confirm'); };
  const confirmAdd = (item) => {
    const newItem = window.ensureExpiry({
      id: item.id + '_' + Date.now(),
      name: item.name,
      emoji: item.emoji,
      mono: item.mono,
      tone: item.tone,
      qty: item.qty,
      unit: item.defaultUnit,
      location: item.loc,
      daysLeft: item.daysLeft,
    });
    setInventory(prev => [newItem, ...prev]);
    setSheet(null);
    setPicked(null);
    // Show toast linking to the matching recipe (avocado → r1)
    const matchingRecipe = RECIPES.find(r => r.uses.includes(item.id)) || RECIPES[0];
    setToast({
      title: lang === 'fr' ? `${localized(lang, item.name)} ajouté` : `${localized(lang, item.name)} added`,
      sub: item.daysLeft <= 3
        ? (lang === 'fr' ? `Expire ${item.daysLeft <= 1 ? 'demain' : `dans ${item.daysLeft}j`} · ${RECIPES.filter(r => r.uses.includes(item.id)).length || 3} recettes` : `Expires ${item.daysLeft <= 1 ? 'tomorrow' : `in ${item.daysLeft}d`} · 3 recipes`)
        : (lang === 'fr' ? `Ajouté à ton inventaire` : `Added to your inventory`),
      recipe: matchingRecipe,
    });
    setTimeout(() => setToast(t => t && t.recipe === matchingRecipe ? null : t), 6000);
  };
  // Bulk-add the common kitchen staples (sel, huile, farine, etc.) at once.
  const bulkAddStaples = () => {
    const staples = ((typeof window !== 'undefined' && window.FOODS) || []).filter(f => f.staple);
    let n = 0;
    setInventory(prev => {
      const have = new Set(prev.map(i => localized(lang, i.name).toLowerCase()));
      const toAdd = staples
        .filter(s => !have.has(localized(lang, s.name).toLowerCase()))
        .map(s => window.ensureExpiry({
          id: s.id + '_' + Date.now() + '_' + Math.random().toString(36).slice(2, 6),
          name: s.name, emoji: s.emoji, mono: s.mono, tone: s.tone,
          qty: s.defaultQty, unit: s.defaultUnit, location: s.defaultLoc, daysLeft: s.defaultDays,
        }));
      n = toAdd.length;
      return [...toAdd, ...prev];
    });
    setSheet(null);
    setToast({
      title: lang === 'fr' ? 'Garde-manger de base ajouté' : 'Pantry staples added',
      sub: lang === 'fr' ? `${n} aliment${n > 1 ? 's' : ''} ajouté${n > 1 ? 's' : ''} à l'inventaire` : `${n} item${n > 1 ? 's' : ''} added to inventory`,
      recipe: null,
    });
    setTimeout(() => setToast(null), 3500);
  };
  const addAll = (items) => {
    const newItems = items.map(item => window.ensureExpiry({
      id: item.id + '_' + Date.now() + '_' + Math.random(),
      name: item.name,
      emoji: item.emoji,
      mono: item.mono,
      tone: item.tone,
      qty: item.defaultQty,
      unit: item.defaultUnit,
      location: item.defaultLoc,
      daysLeft: item.defaultDays,
    }));
    setInventory(prev => [...newItems, ...prev]);
    setSheet(null);
    setToast({
      title: lang === 'fr' ? `${items.length} aliments détectés` : `${items.length} items detected`,
      sub: lang === 'fr' ? `Ajoutés à ton frigo` : `Added to your fridge`,
      recipe: RECIPES[0],
    });
    setTimeout(() => setToast(null), 6000);
  };
  const openRecipe = (r) => {
    setSelectedRecipe(r); setSheet('recipe');
    // Online "lite" cards lack ingredients/steps — fetch full detail on open.
    if (r && r.external && (r.lite || !(r.steps && r.steps.length)) && window.MealDB) {
      window.MealDB.lookup(r.id).then(full => {
        if (full) setSelectedRecipe(prev => (prev && prev.id === r.id ? { ...full, image: full.image || r.image } : prev));
      }).catch(() => {});
    }
  };
  // Resolve missing ingredients for any recipe source (id-linked, QC, online).
  const missingFor = (recipe) => {
    if (window.matchRecipe) return window.matchRecipe(recipe, inventory).missing || [];
    return (recipe.ingredients || []).filter(ing => ing.missing && !ing.pantry);
  };
  const addMissingToGrocery = (missing) => {
    if (!missing || !missing.length) return 0;
    let added = 0;
    setGrocery(prev => {
      const existing = new Set(prev.map(g => (typeof g.name === 'string' ? g.name : localized(lang, g.name)).toLowerCase()));
      const toAdd = missing
        .filter(ing => !existing.has(localized(lang, ing.name).toLowerCase()))
        .map((ing, i) => ({
          id: 'gr_' + (ing.id || 'x') + '_' + Date.now() + '_' + i,
          name: ing.name, emoji: ing.emoji || '🛒',
          aisle: 'fresh', qty: localized(lang, ing.qty) || '',
          checked: false, source: 'recipe',
        }));
      added = toAdd.length;
      return [...prev, ...toAdd];
    });
    return missing.length;
  };
  const handleCooked = () => {
    if (selectedRecipe) {
      // Only deduct inventory items the recipe is explicitly linked to (`uses`),
      // so name-matched catalog/online recipes never silently remove stock.
      const mm = window.matchRecipe ? window.matchRecipe(selectedRecipe, inventory) : { usedIds: [] };
      // Opt-in (Profil → "Mettre à jour l'inventaire en cuisinant", off by
      // default): also remove the perishables this recipe used, by name.
      let deduct = false; try { deduct = localStorage.getItem('pw_deductOnCook') === '1'; } catch (e) {}
      const removeIds = new Set(selectedRecipe.uses || []);
      if (deduct) (mm.usedIds || []).forEach(id => { const it = inventory.find(x => x.id === id); if (it && it.location !== 'pantry') removeIds.add(id); });
      if (removeIds.size) setInventory(prev => prev.filter(i => !removeIds.has(i.id)));
      // Record a REAL stat event: ingredients you had that were about to expire
      // count as "rescued from waste", and their estimated price as money saved.
      try {
        const usedItems = (mm.usedIds || []).map(id => inventory.find(i => i.id === id)).filter(Boolean);
        const rescued = usedItems.filter(i => i.daysLeft != null && i.daysLeft <= 3);
        let region = 'mtl'; try { region = localStorage.getItem('pw_region') || 'mtl'; } catch (e) {}
        const savedCents = rescued.reduce((s, i) => s + Math.round((window.estimatePrice ? window.estimatePrice(i.name, i.qty, region, lang) : 0) * 100), 0);
        setCookLog(prev => [{ ts: Date.now(), rescued: rescued.length, savedCents }, ...prev].slice(0, 400));
      } catch (e) {}
      if (autoAdd) addMissingToGrocery(missingFor(selectedRecipe));
      const leftPortions = Math.max(1, Math.floor((selectedRecipe.portions || 2) / 2));
      setLeftovers(prev => [
        { id: 'lf_' + Date.now(), recipeId: selectedRecipe.id, portions: leftPortions,
          cookedDaysAgo: 0, name: selectedRecipe.name, emoji: selectedRecipe.hero },
        ...prev,
      ]);
    }
    setSheet(null);
    setSelectedRecipe(null);
    setToast({
      title: lang === 'fr' ? 'Bon appétit!' : 'Enjoy!',
      sub: autoAdd
        ? (lang === 'fr' ? 'Manquants → liste · reste sauvé pour lunch' : 'Missing → list · leftovers saved')
        : (lang === 'fr' ? 'Reste sauvé pour le lunch' : 'Leftovers saved for lunch'),
      recipe: null,
    });
    setTimeout(() => setToast(null), 4500);
  };
  const handleAddMissingToList = (missingArg) => {
    const missing = Array.isArray(missingArg) ? missingArg : (selectedRecipe ? missingFor(selectedRecipe) : []);
    const n = addMissingToGrocery(missing);
    setSheet(null);
    setSelectedRecipe(null);
    setToast({
      title: lang === 'fr' ? 'Ajouté à la liste' : 'Added to list',
      sub: lang === 'fr' ? `${n} ingrédient${n > 1 ? 's' : ''} manquant${n > 1 ? 's' : ''} → épicerie` : `${n} missing item${n > 1 ? 's' : ''} → grocery`,
      recipe: null,
    });
    setTimeout(() => setToast(null), 3500);
  };

  // One tap: scan this week's planned meals and add every missing ingredient
  // you don't already have to the grocery list.
  const handleAddWeekToList = () => {
    const start = window.startOfWeekISO(window.todayISO());
    const weekDates = new Set(Array.from({ length: 7 }, (_, i) => window.addDays(start, i)));
    const catalog = RECIPES.concat((window.EXTRA_RECIPES || []), customRecipes);
    const meals = mealPlan.filter(m => weekDates.has(m.date));
    const existing = new Set(grocery.map(g => (typeof g.name === 'string' ? g.name : localized(lang, g.name)).toLowerCase()));
    const seen = new Set(); const toAdd = [];
    meals.forEach(m => {
      const r = catalog.find(x => x.id === m.recipeId);
      if (!r || !window.matchRecipe) return; // online snapshots carry no ingredient list
      window.matchRecipe(r, inventory).missing.forEach(ing => {
        const key = localized(lang, ing.name).toLowerCase();
        if (key && !seen.has(key) && !existing.has(key)) { seen.add(key); toAdd.push(ing); }
      });
    });
    if (toAdd.length) addMissingToGrocery(toAdd);
    setToast({
      title: lang === 'fr' ? 'Épicerie de la semaine' : 'Weekly groceries',
      sub: toAdd.length
        ? (lang === 'fr' ? `${toAdd.length} article${toAdd.length > 1 ? 's' : ''} ajouté${toAdd.length > 1 ? 's' : ''} → liste` : `${toAdd.length} item${toAdd.length > 1 ? 's' : ''} added → list`)
        : (lang === 'fr' ? 'Rien à ajouter — tu as déjà tout !' : 'Nothing to add — you have it all!'),
      recipe: null,
    });
    setTimeout(() => setToast(null), 3500);
  };

  const finishOnboarding = (picks) => {
    if (picks && picks.length > 0) {
      setInventory(prev => [...picks, ...prev]);
    }
    markOnboarded();              // persist flag in localStorage (survives reload)
    setShowOnboarding(false);
    if (setTweak) setTweak('showOnboarding', false); // also flip the design-mode tweak
    // Land on inventory so the user sees what they just added (instead of an empty home that feels dead-end)
    setActiveTab(picks && picks.length > 0 ? 'inv' : 'home');
  };

  // ─── inventory item menu actions ───
  const handleItemDelete = () => {
    if (itemMenu) setInventory(prev => prev.filter(i => i.id !== itemMenu.id));
    setItemMenu(null); setSheet(null);
  };
  const handleItemEdit = () => {
    if (itemMenu) { setEditItemTarget(itemMenu); setSheet('edit-item'); setItemMenu(null); }
  };
  const handleItemUsedSome = () => {
    if (itemMenu) { setUsedSomeTarget(itemMenu); setSheet('used-some'); setItemMenu(null); }
  };
  const handleItemMove = () => {
    if (itemMenu) { setMoveItemTarget(itemMenu); setSheet('move-item'); setItemMenu(null); }
  };
  const handleItemTogglePrivate = () => {
    if (itemMenu) {
      setInventory(prev => prev.map(i => i.id === itemMenu.id ? { ...i, private: !i.private, owner: !i.private ? 'u1' : null } : i));
    }
    setItemMenu(null); setSheet(null);
  };
  const handleSaveEdit = (patch) => {
    if (editItemTarget) {
      setInventory(prev => prev.map(i => i.id === editItemTarget.id ? { ...i, ...patch } : i));
    }
    setEditItemTarget(null); setSheet(null);
  };
  const handleMoveTo = (location) => {
    if (moveItemTarget) {
      setInventory(prev => prev.map(i => i.id === moveItemTarget.id ? { ...i, location } : i));
    }
    setMoveItemTarget(null); setSheet(null);
  };
  const handleUsedQty = (amount) => {
    if (usedSomeTarget) {
      const cur = parseFloat(usedSomeTarget.qty) || 1;
      const next = Math.max(0, cur - amount);
      if (next === 0) {
        setInventory(prev => prev.filter(i => i.id !== usedSomeTarget.id));
      } else {
        setInventory(prev => prev.map(i => i.id === usedSomeTarget.id ? { ...i, qty: String(next) } : i));
      }
      setToast({
        title: lang === 'fr' ? `${localized(lang, usedSomeTarget.name)} ajusté` : `${localized(lang, usedSomeTarget.name)} adjusted`,
        sub: lang === 'fr' ? `−${amount} ${localized(lang, usedSomeTarget.unit) || ''}` : `−${amount} ${localized(lang, usedSomeTarget.unit) || ''}`,
        recipe: null,
      });
      setTimeout(() => setToast(null), 2500);
    }
    setUsedSomeTarget(null); setSheet(null);
  };
  const handleItemSeeRecipes = () => {
    if (itemMenu) {
      const recipes = RECIPES.filter(r => r.uses.includes(itemMenu.id));
      if (recipes.length > 0) {
        openRecipe(recipes[0]);
        return;
      }
    }
    setItemMenu(null); setSheet(null);
    setActiveTab('rec');
  };
  const openItemMenu = (item) => { setItemMenu(item); setSheet('item-menu'); };

  // ─── Meal plan actions ─── (meals keyed by real ISO date)
  const handleSaveMeal = ({ date, slot, recipeId, recipe, cook, time, attendees }) => {
    setMealPlan(prev => {
      const filtered = prev.filter(m => !(m.date === date && m.slot === slot));
      if (!recipeId) return filtered;  // unassign
      // Store a light snapshot so the day cells render any source (catalog or
      // online) without a global lookup.
      const snap = recipe ? { recipeName: recipe.name, recipeHero: recipe.hero, recipeImage: recipe.image || null } : {};
      return [...filtered, { date, slot, recipeId, cook, time, attendees: attendees || {}, status: 'confirmed', ...snap }];
    });
    setMealEditTarget(null); setSheet(null);
  };
  const handleDeleteRecipe = (recipe) => {
    if (!recipe || !recipe.custom) return;
    const msg = lang === 'fr' ? 'Supprimer cette recette ?' : 'Delete this recipe?';
    if (typeof window !== 'undefined' && !window.confirm(msg)) return;
    setCustomRecipes(prev => prev.filter(r => r.id !== recipe.id));
    setSheet(null); setSelectedRecipe(null);
    setToast({ title: lang === 'fr' ? 'Recette supprimée' : 'Recipe deleted', sub: '', recipe: null });
    setTimeout(() => setToast(null), 2500);
  };
  const handleEditRecipe = (recipe) => {
    if (!recipe || !recipe.custom) return;
    setEditRecipeTarget(recipe);
    setSelectedRecipe(null);
    setSheet('add-recipe');
  };
  // From a recipe → open the meal planner pre-filled with this recipe.
  const handleAddRecipeToPlan = (recipe) => {
    const slot = recipe && recipe.mealType ? recipe.mealType : 'dinner';
    setSelectedRecipe(null);
    setMealEditTarget({ date: (window.todayISO ? window.todayISO() : new Date().toISOString().slice(0, 10)), slot, presetRecipe: recipe });
    setSheet('meal-edit');
  };
  const handleRemoveMeal = ({ date, slot }) => {
    setMealPlan(prev => prev.filter(m => !(m.date === date && m.slot === slot)));
    setMealEditTarget(null); setSheet(null);
  };

  // ─── Family ───
  const handleAddFamilyMember = (member) => {
    // Avoid duplicates (same email) — the sheet may call this then keep open
    // to show the generated link.
    setHousehold(prev => {
      const exists = member.email && prev.some(m => (m.email || '').toLowerCase() === member.email.toLowerCase());
      return exists ? prev : [...prev, member];
    });
  };

  // ─── Custom recipes ───
  const handleAddCustomRecipe = (recipe) => {
    if (editRecipeTarget) {
      // Editing an existing custom recipe → replace in place, keep its id.
      const updated = { ...recipe, id: editRecipeTarget.id, custom: true };
      setCustomRecipes(prev => prev.map(r => r.id === editRecipeTarget.id ? updated : r));
      setEditRecipeTarget(null);
      setSheet(null);
      setToast({ title: lang === 'fr' ? 'Recette modifiée' : 'Recipe updated', sub: localized(lang, recipe.name), recipe: null });
      setTimeout(() => setToast(null), 3000);
      return;
    }
    setCustomRecipes(prev => [recipe, ...prev]);
    setSheet(null);
    setToast({
      title: lang === 'fr' ? 'Recette ajoutée' : 'Recipe added',
      sub: localized(lang, recipe.name),
      recipe,
    });
    setTimeout(() => setToast(null), 3500);
  };

  // ─── Grocery detailed ───
  const handleAddGroceryDetailed = (item) => {
    setGrocery(prev => [...prev, { ...item, id: 'g_' + Date.now() }]);
    setSheet(null);
  };

  // ─── grocery check → buy confirm → add to inventory ───
  const openBuyConfirm = (groceryItem) => { setBuyTarget(groceryItem); setSheet('buy-confirm'); };
  const handleBuyConfirm = ({ count, size, unit }) => {
    if (buyTarget) {
      // Use the food DB's typical shelf life for the bought item when we can.
      const food = (window.ADD_CATALOG || []).find(f => localized(lang, f.name).toLowerCase() === (typeof buyTarget.name === 'string' ? buyTarget.name : localized(lang, buyTarget.name)).toLowerCase());
      const newItem = window.ensureExpiry({
        id: 'inv_' + Date.now(),
        name: buyTarget.name,
        emoji: buyTarget.emoji || '🛒',
        mono: (typeof buyTarget.name === 'string' ? buyTarget.name : localized(lang, buyTarget.name)).charAt(0).toUpperCase(),
        tone: '#D8DAE0',
        qty: count > 1 ? `${count}×${size}` : size,
        unit: unit,
        location: food ? food.defaultLoc : 'fridge',
        daysLeft: food ? food.defaultDays : 7,
      });
      setInventory(prev => [newItem, ...prev]);
      // remove from grocery (it's been bought)
      setGrocery(prev => prev.filter(g => g.id !== buyTarget.id));
      setToast({
        title: lang === 'fr' ? `${typeof buyTarget.name === 'string' ? buyTarget.name : localized(lang, buyTarget.name)} ajouté au frigo` : `Added to fridge`,
        sub: `${count} × ${size} ${unit}`,
        recipe: null,
      });
      setTimeout(() => setToast(null), 3500);
    }
    setBuyTarget(null); setSheet(null);
  };

  // ─── quick log handler ───
  const handleQuickLog = ({ items }) => {
    if (items && items.length > 0) {
      setInventory(prev => prev.filter(i => !items.includes(i.id)));
      setToast({
        title: lang === 'fr' ? 'Inventaire mis à jour' : 'Inventory updated',
        sub: lang === 'fr' ? `${items.length} aliment(s) retiré(s)` : `${items.length} item(s) removed`,
        recipe: null,
      });
      setTimeout(() => setToast(null), 3000);
    }
    setSheet(null);
  };

  // ─── shared content sections ───
  const renderHeader = () => <HomeHeader palette={palette} lang={lang} onToggleLang={onChangeLang} onOpenProfile={() => setActiveTab('prof')} authUser={authUser} />;
  const renderStat = () => dashStyle === 'hero'
    ? <HeroStat palette={palette} lang={lang} inventory={inventory} onTapAtRisk={() => setActiveTab('rec')} />
    : <StatCards palette={palette} lang={lang} inventory={inventory} />;
  const renderExpiring = () => (
    <>
      <SectionHeader palette={palette} title={t(lang, 'soon')} action={{ label: t(lang, 'seeAll'), onClick: () => {} }} />
      <ExpiringStrip items={expiringItems} palette={palette} lang={lang} iconStyle={iconStyle} onPick={() => openRecipe(suggestedRecipe)} />
    </>
  );
  const renderRecipe = () => (
    <>
      <SectionHeader palette={palette} title={t(lang, 'suggestedToday')} />
      <div style={{ padding: '0 20px 16px' }}>
        <RecipeCard recipe={suggestedRecipe} inventory={inventory} palette={palette} lang={lang} style={recipeStyle} onOpen={() => openRecipe(suggestedRecipe)} />
      </div>
    </>
  );
  const renderInv = () => (
    <>
      <SectionHeader palette={palette} title={t(lang, 'inventoryShort')} action={{ label: `${inventory.length} ${t(lang, 'totalItems')}`, onClick: () => {} }} />
      <InventoryList items={sortedInv} palette={palette} lang={lang} iconStyle={iconStyle} density={density} web={web} onPick={() => {}} />
    </>
  );

  // ─── Active page renderer (mobile) ───
  const renderHomePage = () => (
    <>
      {renderHeader()}
      <RemindersCard palette={palette} lang={lang} mealPlan={mealPlan} inventory={inventory} grocery={grocery}
        onOpenPlan={() => setActiveTab('plan')} onOpenGrocery={() => setActiveTab('list')} onOpenInv={() => setActiveTab('inv')} />
      <WasteScoreCard palette={palette} lang={lang} cookLog={cookLog} />
      {renderStat()}
      {renderExpiring()}
      {renderRecipe()}
      <LeftoversCard palette={palette} lang={lang} leftovers={leftovers} onPlan={() => setActiveTab('plan')} />
      <SectionHeader palette={palette} title={t(lang, 'planTitle')} action={{ label: t(lang, 'seeAll'), onClick: () => setActiveTab('plan') }} />
      <WeekPlanStrip palette={palette} lang={lang} mealPlan={mealPlan} onOpenPlan={() => setActiveTab('plan')} />
      {renderInv()}
      <div style={{ height: 24 }} />
    </>
  );
  const renderActivePage = () => {
    if (activeTab === 'home') return renderHomePage();
    if (activeTab === 'inv') return (
      <InventoryPage palette={palette} lang={lang} iconStyle={iconStyle}
        inventory={inventory} setInventory={setInventory}
        openAdd={openAdd} onToggleLang={onChangeLang} onOpenProfile={() => setActiveTab('prof')}
        onOpenItemMenu={openItemMenu} onBack={() => setActiveTab('home')} web={false} authUser={authUser} />
    );
    if (activeTab === 'rec') return (
      <RecipesPage palette={palette} lang={lang} inventory={inventory}
        favorites={favorites} setFavorites={setFavorites}
        customRecipes={customRecipes} setCustomRecipes={setCustomRecipes}
        openRecipe={openRecipe} onAddRecipe={() => setSheet('add-recipe')}
        onBack={() => setActiveTab('home')}
        onToggleLang={onChangeLang} onOpenProfile={() => setActiveTab('prof')} web={false} authUser={authUser} />
    );
    if (activeTab === 'list') return (
      <GroceryPage palette={palette} lang={lang} inventory={inventory}
        grocery={grocery} setGrocery={setGrocery} onBuyConfirm={openBuyConfirm}
        onAddDetailed={() => setSheet('add-grocery')}
        onBack={() => setActiveTab('home')}
        onToggleLang={onChangeLang} onOpenProfile={() => setActiveTab('prof')} web={false} authUser={authUser} />
    );
    if (activeTab === 'plan') return (
      <MealPlanPage palette={palette} lang={lang} inventory={inventory}
        household={household} privacyPrefs={privacyPrefs}
        mealPlan={mealPlan} setMealPlan={setMealPlan} leftovers={leftovers}
        openRecipe={openRecipe} onBack={() => setActiveTab('home')}
        onAssignMeal={(meal) => { setMealEditTarget(meal); setSheet('meal-edit'); }} onAddWeekToList={handleAddWeekToList}
        onToggleLang={onChangeLang} onOpenProfile={() => setActiveTab('prof')} web={false} authUser={authUser} />
    );
    if (activeTab === 'family') return (
      <FamilyHubPage palette={palette} lang={lang}
        household={household} setHousehold={setHousehold} authUser={authUser}
        mealPlan={mealPlan} grocery={grocery}
        privacyPrefs={privacyPrefs} setPrivacyPrefs={setPrivacyPrefs}
        onInviteFamily={() => setSheet('invite-family')}
        onOpenPlan={() => setActiveTab('plan')} onOpenList={() => setActiveTab('list')}
        onBack={() => setActiveTab('home')}
        onToggleLang={onChangeLang} onOpenProfile={() => setActiveTab('prof')} />
    );
    if (activeTab === 'prof') return (
      <ProfilePage palette={palette} lang={lang} inventory={inventory} leftovers={leftovers} cookLog={cookLog} onOpenAdmin={() => setSheet("admin")}
        household={household} setHousehold={setHousehold}
        privacyPrefs={privacyPrefs} setPrivacyPrefs={setPrivacyPrefs}
        onInviteFamily={() => setSheet('invite-family')}
        onBack={() => setActiveTab('home')}
        onToggleLang={onChangeLang} setTweak={setTweak} tweaks={tweaks}
        authUser={authUser} onSignOut={onSignOut} onShowLogin={onShowLogin} authStatus={authStatus} syncStatus={sync}
        onOpenSavedExplainer={() => setSheet('saved-explainer')} web={false} />
    );
    return renderHomePage();
  };

  // ─── WEB LAYOUT ───
  if (web) {
    const webPageTitle =
      activeTab === 'inv' ? t(lang, 'invTitle') :
      activeTab === 'rec' ? t(lang, 'recTitle') :
      activeTab === 'list' ? t(lang, 'grocTitle') :
      activeTab === 'plan' ? t(lang, 'planTitle') :
      activeTab === 'family' ? t(lang, 'familySpace') :
      activeTab === 'prof' ? t(lang, 'profTitle') :
      t(lang, 'home');
    return (
      <div style={{ display: 'flex', height: '100%', background: palette.bg, color: palette.ink, position: 'relative' }}>
        <TabNav variant="side" active={activeTab} onChange={(id) => id === 'add' ? openAdd() : setActiveTab(id)} palette={palette} lang={lang} iconStyle={iconStyle} />
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0, position: 'relative' }}>
          {/* top bar */}
          <div style={{
            display: 'flex', alignItems: 'center', gap: 12,
            padding: '14px 28px', borderBottom: `1px solid ${palette.line}`,
            background: palette.bg,
          }}>
            <div style={{ flex: 1, fontSize: 18, fontWeight: 600, color: palette.ink, letterSpacing: -0.3 }}>
              {webPageTitle}
            </div>
            <div style={{
              flex: 1, maxWidth: 340, height: 34,
              background: palette.cardSub, borderRadius: 10, border: `1px solid ${palette.line}`,
              display: 'flex', alignItems: 'center', gap: 8, padding: '0 12px', color: palette.ink3, fontSize: 13,
            }}>
              <Icon name="search" size={15} color={palette.ink3} />
              <span>{t(lang, 'searchPh')}</span>
            </div>
            <div onClick={onChangeLang} style={{
              height: 30, padding: '0 10px', borderRadius: 8,
              background: palette.cardSub, color: palette.ink2, border: `1px solid ${palette.line}`,
              display: 'flex', alignItems: 'center', cursor: 'pointer',
              fontSize: 11, fontFamily: 'Geist Mono, monospace', fontWeight: 500, letterSpacing: 0.06,
            }}>{lang.toUpperCase()}</div>
            <UserAvatar palette={palette} size={34} onClick={() => setActiveTab('prof')}
              name={authUser?.name} userId={authUser?.id} />
          </div>
          {/* main content */}
          {activeTab === 'home' ? (
            <div style={{ flex: 1, overflowY: 'auto', overflowX: 'hidden', display: 'grid', gridTemplateColumns: '1.4fr 1fr', gap: 0, position: 'relative' }}>
              <div style={{ padding: '0 8px 24px 12px', borderRight: `1px solid ${palette.line}`, minWidth: 0 }}>
                <div style={{ padding: '24px 20px 4px' }}>
                  <div style={{ fontSize: 11.5, color: palette.ink3, fontFamily: 'Geist Mono, monospace', letterSpacing: 0.04, textTransform: 'uppercase', marginBottom: 4 }}>{t(lang, 'todayLabel')}</div>
                  <div style={{ fontSize: 26, fontWeight: 600, letterSpacing: -0.6, color: palette.ink }}>{t(lang, 'greeting')}</div>
                </div>
                <RemindersCard palette={palette} lang={lang} mealPlan={mealPlan} inventory={inventory} grocery={grocery}
                  onOpenPlan={() => setActiveTab('plan')} onOpenGrocery={() => setActiveTab('list')} onOpenInv={() => setActiveTab('inv')} />
                <WasteScoreCard palette={palette} lang={lang} cookLog={cookLog} />
                {renderStat()}
                <LeftoversCard palette={palette} lang={lang} leftovers={leftovers} onPlan={() => setActiveTab('plan')} />
                <SectionHeader palette={palette} title={t(lang, 'planTitle')} action={{ label: t(lang, 'seeAll'), onClick: () => setActiveTab('plan') }} />
                <WeekPlanStrip palette={palette} lang={lang} mealPlan={mealPlan} onOpenPlan={() => setActiveTab('plan')} />
                {renderExpiring()}
                {renderRecipe()}
              </div>
              <div style={{ padding: '24px 12px 24px 8px', minWidth: 0 }}>
                <SectionHeader palette={palette} title={t(lang, 'inventoryShort')} action={{ label: `${inventory.length} ${t(lang, 'totalItems')}`, onClick: () => setActiveTab('inv') }} />
                <InventoryList items={sortedInv} palette={palette} lang={lang} iconStyle={iconStyle} density="compact" web={false} onPick={() => {}} />
              </div>
            </div>
          ) : activeTab === 'inv' ? (
            <InventoryPage palette={palette} lang={lang} iconStyle={iconStyle}
              inventory={inventory} setInventory={setInventory}
              openAdd={openAdd} onToggleLang={onChangeLang}
              onOpenItemMenu={openItemMenu} web={true} />
          ) : activeTab === 'rec' ? (
            <RecipesPage palette={palette} lang={lang} inventory={inventory}
              favorites={favorites} setFavorites={setFavorites}
              customRecipes={customRecipes} setCustomRecipes={setCustomRecipes}
              openRecipe={openRecipe} onAddRecipe={() => setSheet('add-recipe')}
              onToggleLang={onChangeLang} web={true} authUser={authUser} />
          ) : activeTab === 'list' ? (
            <GroceryPage palette={palette} lang={lang} inventory={inventory}
              grocery={grocery} setGrocery={setGrocery} onBuyConfirm={openBuyConfirm}
              onAddDetailed={() => setSheet('add-grocery')}
              onToggleLang={onChangeLang} web={true} authUser={authUser} />
          ) : activeTab === 'plan' ? (
            <MealPlanPage palette={palette} lang={lang} inventory={inventory}
              household={household} privacyPrefs={privacyPrefs}
              mealPlan={mealPlan} setMealPlan={setMealPlan} leftovers={leftovers}
              openRecipe={openRecipe} onBack={() => setActiveTab('home')}
              onAssignMeal={(meal) => { setMealEditTarget(meal); setSheet('meal-edit'); }} onAddWeekToList={handleAddWeekToList}
              onToggleLang={onChangeLang} web={true} authUser={authUser} />
          ) : activeTab === 'family' ? (
            <FamilyHubPage palette={palette} lang={lang}
              household={household} setHousehold={setHousehold} authUser={authUser}
              mealPlan={mealPlan} grocery={grocery}
              privacyPrefs={privacyPrefs} setPrivacyPrefs={setPrivacyPrefs}
              onInviteFamily={() => setSheet('invite-family')}
              onOpenPlan={() => setActiveTab('plan')} onOpenList={() => setActiveTab('list')}
              onToggleLang={onChangeLang} />
          ) : (
            <ProfilePage palette={palette} lang={lang} inventory={inventory} leftovers={leftovers} cookLog={cookLog} onOpenAdmin={() => setSheet("admin")}
              household={household} setHousehold={setHousehold}
              privacyPrefs={privacyPrefs} setPrivacyPrefs={setPrivacyPrefs}
              onInviteFamily={() => setSheet('invite-family')}
              onToggleLang={onChangeLang} setTweak={setTweak} tweaks={tweaks}
              authUser={authUser} onSignOut={onSignOut} onShowLogin={onShowLogin} authStatus={authStatus} syncStatus={sync}
              onOpenSavedExplainer={() => setSheet('saved-explainer')} web={true} />
          )}
          {/* sheets */}
          <Sheet open={sheet === 'add-method'} onClose={() => setSheet(null)} palette={palette} web>
            <AddMethodPicker palette={palette} lang={lang} onPick={pickMethod} onClose={() => setSheet(null)} />
          </Sheet>
          <Sheet open={sheet === 'add-manual'} onClose={() => setSheet(null)} palette={palette} web>
            <ManualAdd palette={palette} lang={lang} onBack={() => setSheet('add-method')} onPick={pickSuggestion} onBulkAdd={bulkAddStaples} />
          </Sheet>
          <Sheet open={sheet === 'add-photo'} onClose={() => setSheet(null)} palette={palette} web>
            <AIPhotoAdd palette={palette} lang={lang} onBack={() => setSheet('add-method')} onAddAll={addAll} />
          </Sheet>
          <Sheet open={sheet === 'add-receipt'} onClose={() => setSheet(null)} palette={palette} web>
            <AIPhotoAdd palette={palette} lang={lang} mode="receipt" onBack={() => setSheet('add-method')} onAddAll={addAll} />
          </Sheet>
          <Sheet open={sheet === 'add-barcode'} onClose={() => setSheet(null)} palette={palette} web>
            <BarcodeAdd palette={palette} lang={lang} onBack={() => setSheet('add-method')} onPick={pickSuggestion} />
          </Sheet>
          <Sheet open={sheet === 'add-confirm'} onClose={() => { setSheet(null); setPicked(null); }} palette={palette} web>
            {picked && <ConfirmAdd palette={palette} lang={lang} suggestion={picked} onBack={() => setSheet('add-manual')} onConfirm={confirmAdd} />}
          </Sheet>
          <Sheet open={sheet === 'recipe'} onClose={() => { setSheet(null); setSelectedRecipe(null); }} palette={palette} web maxHeight="92%">
            {selectedRecipe && <RecipeDetail palette={palette} lang={lang} recipe={selectedRecipe} inventory={inventory} onClose={() => { setSheet(null); setSelectedRecipe(null); }} onCooked={handleCooked} onAddMissingToList={handleAddMissingToList} onAddToPlan={handleAddRecipeToPlan} onDeleteRecipe={handleDeleteRecipe} onEditRecipe={handleEditRecipe} autoAdd={autoAdd} setAutoAdd={(v) => { setAutoAdd(v); if (setTweak) setTweak('autoAddMissing', v); }} />}
          </Sheet>
          <Sheet open={sheet === 'admin'} onClose={() => setSheet(null)} palette={palette} web maxHeight="92%">
            <AdminPanel palette={palette} lang={lang} onClose={() => setSheet(null)} />
          </Sheet>
          <Sheet open={sheet === 'item-menu'} onClose={() => { setSheet(null); setItemMenu(null); }} palette={palette} web maxHeight="70%">
            {itemMenu && <DotsActionSheet palette={palette} lang={lang} item={itemMenu} inventory={inventory}
              onClose={() => { setSheet(null); setItemMenu(null); }}
              onEdit={handleItemEdit}
              onMove={handleItemMove}
              onDelete={handleItemDelete}
              onUsedSome={handleItemUsedSome}
              onTogglePrivate={handleItemTogglePrivate}
              onSeeRecipes={handleItemSeeRecipes} />}
          </Sheet>
          <Sheet open={sheet === 'edit-item'} onClose={() => { setSheet(null); setEditItemTarget(null); }} palette={palette} web>
            {editItemTarget && <EditItemSheet palette={palette} lang={lang} item={editItemTarget}
              onClose={() => { setSheet(null); setEditItemTarget(null); }} onSave={handleSaveEdit} />}
          </Sheet>
          <Sheet open={sheet === 'move-item'} onClose={() => { setSheet(null); setMoveItemTarget(null); }} palette={palette} web>
            {moveItemTarget && <MoveItemSheet palette={palette} lang={lang} item={moveItemTarget}
              onClose={() => { setSheet(null); setMoveItemTarget(null); }} onMove={handleMoveTo} />}
          </Sheet>
          <Sheet open={sheet === 'used-some'} onClose={() => { setSheet(null); setUsedSomeTarget(null); }} palette={palette} web>
            {usedSomeTarget && <UsedSomeSheet palette={palette} lang={lang} item={usedSomeTarget}
              onClose={() => { setSheet(null); setUsedSomeTarget(null); }} onConfirm={handleUsedQty} />}
          </Sheet>
          <Sheet open={sheet === 'meal-edit'} onClose={() => { setSheet(null); setMealEditTarget(null); }} palette={palette} web maxHeight="92%">
            {mealEditTarget && <MealEditSheet palette={palette} lang={lang} target={mealEditTarget}
              household={household} recipes={[...customRecipes, ...RECIPES, ...((typeof window !== 'undefined' && window.EXTRA_RECIPES) || [])]} inventory={inventory}
              onClose={() => { setSheet(null); setMealEditTarget(null); }}
              onSave={handleSaveMeal} onRemove={handleRemoveMeal} />}
          </Sheet>
          <Sheet open={sheet === 'invite-family'} onClose={() => setSheet(null)} palette={palette} web>
            <InviteFamilySheet palette={palette} lang={lang}
              onClose={() => setSheet(null)} onAdd={handleAddFamilyMember} />
          </Sheet>
          <Sheet open={sheet === 'add-recipe'} onClose={() => setSheet(null)} palette={palette} web maxHeight="92%">
            <AddRecipeSheet palette={palette} lang={lang} initial={editRecipeTarget}
              onClose={() => { setSheet(null); setEditRecipeTarget(null); }} onAdd={handleAddCustomRecipe} />
          </Sheet>
          <Sheet open={sheet === 'add-grocery'} onClose={() => setSheet(null)} palette={palette} web>
            <AddGroceryDetailedSheet palette={palette} lang={lang} inventory={inventory} grocery={grocery}
              onClose={() => setSheet(null)} onAdd={handleAddGroceryDetailed} />
          </Sheet>
          <Sheet open={sheet === 'quick-log'} onClose={() => setSheet(null)} palette={palette} web maxHeight="85%">
            <QuickLogSheet palette={palette} lang={lang} inventory={inventory}
              onClose={() => setSheet(null)} onLog={handleQuickLog} />
          </Sheet>
          <Sheet open={sheet === 'buy-confirm'} onClose={() => { setSheet(null); setBuyTarget(null); }} palette={palette} web>
            {buyTarget && <BuyConfirmSheet palette={palette} lang={lang} groceryItem={buyTarget}
              onClose={() => { setSheet(null); setBuyTarget(null); }} onConfirm={handleBuyConfirm} />}
          </Sheet>
          <Sheet open={sheet === 'saved-explainer'} onClose={() => setSheet(null)} palette={palette} web>
            <SavedExplainerSheet palette={palette} lang={lang} onClose={() => setSheet(null)} />
          </Sheet>
          <Toast toast={toast} onTap={() => toast?.recipe && openRecipe(toast.recipe)} onDismiss={() => setToast(null)} palette={palette} lang={lang} web />
          {showOnboarding && (
            <Onboarding palette={palette} lang={lang} onComplete={finishOnboarding} onToggleLang={onChangeLang} web={true} authUser={authUser} />
          )}
        </div>
      </div>
    );
  }

  // ─── MOBILE LAYOUT ───
  return (
    <div style={{
      display: 'flex', flexDirection: 'column', height: '100%',
      background: palette.bg, color: palette.ink,
      paddingTop: topInset || 0,
      position: 'relative', overflow: 'hidden',
    }}>
      <div style={{ flex: 1, overflowY: 'auto', overflowX: 'hidden', display: 'block' }}>
        {renderActivePage()}
      </div>
      <TabNav variant="bottom" active={activeTab} onChange={(id) => id === 'add' ? openAdd() : setActiveTab(id)} palette={palette} lang={lang} iconStyle={iconStyle} />

      {/* sheets */}
      <Sheet open={sheet === 'add-method'} onClose={() => setSheet(null)} palette={palette}>
        <AddMethodPicker palette={palette} lang={lang} onPick={pickMethod} onClose={() => setSheet(null)} />
      </Sheet>
      <Sheet open={sheet === 'add-manual'} onClose={() => setSheet(null)} palette={palette}>
        <ManualAdd palette={palette} lang={lang} onBack={() => setSheet('add-method')} onPick={pickSuggestion} onBulkAdd={bulkAddStaples} />
      </Sheet>
      <Sheet open={sheet === 'add-photo'} onClose={() => setSheet(null)} palette={palette}>
        <AIPhotoAdd palette={palette} lang={lang} onBack={() => setSheet('add-method')} onAddAll={addAll} />
      </Sheet>
      <Sheet open={sheet === 'add-receipt'} onClose={() => setSheet(null)} palette={palette}>
        <AIPhotoAdd palette={palette} lang={lang} mode="receipt" onBack={() => setSheet('add-method')} onAddAll={addAll} />
      </Sheet>
      <Sheet open={sheet === 'add-barcode'} onClose={() => setSheet(null)} palette={palette}>
        <BarcodeAdd palette={palette} lang={lang} onBack={() => setSheet('add-method')} onPick={pickSuggestion} />
      </Sheet>
      <Sheet open={sheet === 'add-confirm'} onClose={() => { setSheet(null); setPicked(null); }} palette={palette}>
        {picked && <ConfirmAdd palette={palette} lang={lang} suggestion={picked} onBack={() => setSheet('add-manual')} onConfirm={confirmAdd} />}
      </Sheet>
      <Sheet open={sheet === 'recipe'} onClose={() => { setSheet(null); setSelectedRecipe(null); }} palette={palette} maxHeight="92%">
        {selectedRecipe && <RecipeDetail palette={palette} lang={lang} recipe={selectedRecipe} inventory={inventory} onClose={() => { setSheet(null); setSelectedRecipe(null); }} onCooked={handleCooked} onAddMissingToList={handleAddMissingToList} onAddToPlan={handleAddRecipeToPlan} onDeleteRecipe={handleDeleteRecipe} onEditRecipe={handleEditRecipe} autoAdd={autoAdd} setAutoAdd={(v) => { setAutoAdd(v); if (setTweak) setTweak('autoAddMissing', v); }} />}
      </Sheet>
      <Sheet open={sheet === 'admin'} onClose={() => setSheet(null)} palette={palette} maxHeight="92%">
        <AdminPanel palette={palette} lang={lang} onClose={() => setSheet(null)} />
      </Sheet>
      <Sheet open={sheet === 'item-menu'} onClose={() => { setSheet(null); setItemMenu(null); }} palette={palette} maxHeight="70%">
        {itemMenu && <DotsActionSheet palette={palette} lang={lang} item={itemMenu} inventory={inventory}
          onClose={() => { setSheet(null); setItemMenu(null); }}
          onEdit={handleItemEdit}
          onMove={handleItemMove}
          onDelete={handleItemDelete}
          onUsedSome={handleItemUsedSome}
          onTogglePrivate={handleItemTogglePrivate}
          onSeeRecipes={handleItemSeeRecipes} />}
      </Sheet>
      <Sheet open={sheet === 'edit-item'} onClose={() => { setSheet(null); setEditItemTarget(null); }} palette={palette}>
        {editItemTarget && <EditItemSheet palette={palette} lang={lang} item={editItemTarget}
          onClose={() => { setSheet(null); setEditItemTarget(null); }} onSave={handleSaveEdit} />}
      </Sheet>
      <Sheet open={sheet === 'move-item'} onClose={() => { setSheet(null); setMoveItemTarget(null); }} palette={palette}>
        {moveItemTarget && <MoveItemSheet palette={palette} lang={lang} item={moveItemTarget}
          onClose={() => { setSheet(null); setMoveItemTarget(null); }} onMove={handleMoveTo} />}
      </Sheet>
      <Sheet open={sheet === 'used-some'} onClose={() => { setSheet(null); setUsedSomeTarget(null); }} palette={palette}>
        {usedSomeTarget && <UsedSomeSheet palette={palette} lang={lang} item={usedSomeTarget}
          onClose={() => { setSheet(null); setUsedSomeTarget(null); }} onConfirm={handleUsedQty} />}
      </Sheet>
      <Sheet open={sheet === 'meal-edit'} onClose={() => { setSheet(null); setMealEditTarget(null); }} palette={palette} maxHeight="92%">
        {mealEditTarget && <MealEditSheet palette={palette} lang={lang} target={mealEditTarget}
          household={household} recipes={[...customRecipes, ...RECIPES, ...((typeof window !== 'undefined' && window.EXTRA_RECIPES) || [])]} inventory={inventory}
          onClose={() => { setSheet(null); setMealEditTarget(null); }}
          onSave={handleSaveMeal} onRemove={handleRemoveMeal} />}
      </Sheet>
      <Sheet open={sheet === 'invite-family'} onClose={() => setSheet(null)} palette={palette}>
        <InviteFamilySheet palette={palette} lang={lang}
          onClose={() => setSheet(null)} onAdd={handleAddFamilyMember} />
      </Sheet>
      <Sheet open={sheet === 'add-recipe'} onClose={() => setSheet(null)} palette={palette} maxHeight="92%">
        <AddRecipeSheet palette={palette} lang={lang} initial={editRecipeTarget}
              onClose={() => { setSheet(null); setEditRecipeTarget(null); }} onAdd={handleAddCustomRecipe} />
      </Sheet>
      <Sheet open={sheet === 'add-grocery'} onClose={() => setSheet(null)} palette={palette}>
        <AddGroceryDetailedSheet palette={palette} lang={lang} inventory={inventory} grocery={grocery}
          onClose={() => setSheet(null)} onAdd={handleAddGroceryDetailed} />
      </Sheet>
      <Sheet open={sheet === 'quick-log'} onClose={() => setSheet(null)} palette={palette} maxHeight="85%">
        <QuickLogSheet palette={palette} lang={lang} inventory={inventory}
          onClose={() => setSheet(null)} onLog={handleQuickLog} />
      </Sheet>
      <Sheet open={sheet === 'buy-confirm'} onClose={() => { setSheet(null); setBuyTarget(null); }} palette={palette}>
        {buyTarget && <BuyConfirmSheet palette={palette} lang={lang} groceryItem={buyTarget}
          onClose={() => { setSheet(null); setBuyTarget(null); }} onConfirm={handleBuyConfirm} />}
      </Sheet>
      <Sheet open={sheet === 'saved-explainer'} onClose={() => setSheet(null)} palette={palette}>
        <SavedExplainerSheet palette={palette} lang={lang} onClose={() => setSheet(null)} />
      </Sheet>

      <Toast toast={toast} onTap={() => toast?.recipe && openRecipe(toast.recipe)} onDismiss={() => setToast(null)} palette={palette} lang={lang} />

      {showOnboarding && (
        <Onboarding palette={palette} lang={lang} onComplete={finishOnboarding} onToggleLang={onChangeLang} web={false} />
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Toast — sliding card at top
// ─────────────────────────────────────────────────────────────
function Toast({ toast, onTap, onDismiss, palette, lang, web }) {
  const [shown, setShown] = useStateA(false);
  useEffectA(() => {
    if (toast) {
      const t = setTimeout(() => setShown(true), 10);
      return () => clearTimeout(t);
    } else {
      setShown(false);
    }
  }, [toast]);
  if (!toast) return null;
  return (
    <div style={{
      position: 'absolute', top: web ? 70 : 56, left: 16, right: 16, zIndex: 90,
      transform: shown ? 'translateY(0)' : 'translateY(-120%)',
      opacity: shown ? 1 : 0,
      transition: 'transform 320ms cubic-bezier(0.2,0.8,0.2,1), opacity 280ms',
      pointerEvents: shown ? 'auto' : 'none',
    }}>
      <div onClick={onTap} style={{
        background: palette.ink, color: palette.bg,
        borderRadius: 14, padding: '12px 14px',
        display: 'flex', alignItems: 'center', gap: 12, cursor: toast.recipe ? 'pointer' : 'default',
        boxShadow: '0 12px 32px rgba(0,0,0,0.25)',
      }}>
        <div style={{
          width: 36, height: 36, borderRadius: 10,
          background: palette.primary, color: palette.primaryInk,
          display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
        }}>
          <Icon name="check" size={18} color={palette.primaryInk} />
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 13.5, fontWeight: 500, letterSpacing: -0.1 }}>{toast.title}</div>
          <div style={{ fontSize: 11.5, opacity: 0.7, marginTop: 1 }}>{toast.sub}</div>
        </div>
        {toast.recipe && <Icon name="chevR" size={16} color={palette.bg} />}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Root App — frames + tweaks
// ─────────────────────────────────────────────────────────────
function App() {
  const [tw, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const palette = PALETTES[tw.palette] || PALETTES.sage;
  const lang = tw.lang;
  const onChangeLang = () => setTweak('lang', lang === 'fr' ? 'en' : 'fr');

  // Auth — single source of truth at the root. Available to children via prop.
  // (window.PWAuth.useAuth is defined in pantry-auth.jsx, loaded before this file.)
  const auth = (window.PWAuth && window.PWAuth.useAuth) ? window.PWAuth.useAuth() : { user: null, status: 'guest', signIn: () => {}, signUp: () => {}, signOut: () => {}, skipAuth: () => {} };
  useEffectA(() => {
    window.PWAuth = window.PWAuth || {};
    window.PWAuth.current = auth;
  }, [auth.user, auth.status]);

  // Detect PWA standalone mode OR mobile viewport OR top-level (non-iframed) page.
  // The key insight: when the page is loaded as a real URL (not embedded in the
  // design-tool's iframe), `window.parent === window`. We always show the
  // standalone layout + auth gate in that case so real visitors get the app,
  // not the design preview with hardcoded demo data.
  const [isStandalone, setIsStandalone] = useStateA(() => {
    if (typeof window === 'undefined') return false;
    const notIframed = window.parent === window;
    const standalone = window.matchMedia('(display-mode: standalone)').matches ||
           window.navigator.standalone === true ||
           window.location.search.includes('standalone=1');
    const isMobileViewport = window.innerWidth <= 500;
    return notIframed || standalone || isMobileViewport;
  });
  useEffectA(() => {
    const mq = window.matchMedia('(display-mode: standalone)');
    const onChange = () => setIsStandalone(
      window.parent === window ||
      mq.matches || window.navigator.standalone === true || window.innerWidth <= 500
    );
    mq.addEventListener?.('change', onChange);
    window.addEventListener('resize', onChange);
    return () => {
      mq.removeEventListener?.('change', onChange);
      window.removeEventListener('resize', onChange);
    };
  }, []);

  // Sign-out wrapper that ALSO clears local app data so the next user
  // doesn't inherit the previous user's inventory/grocery/etc.
  const handleSignOut = async () => {
    // Flush any pending (debounced) sync push so the last change reaches the
    // server before we wipe local data. Only clear localStorage if the save
    // succeeded — otherwise keep it so an offline edit isn't lost.
    let savedOk = true;
    try {
      if (window.PWAuth && window.PWAuth.flushSync) {
        const res = await window.PWAuth.flushSync();
        savedOk = !res || res.ok !== false;
      }
    } catch (e) { savedOk = false; }
    try {
      if (savedOk) {
        localStorage.removeItem('pantrywise.v1');
        localStorage.removeItem('pantrywise.onboarded');
      }
      localStorage.removeItem('pantrywise.authSkipped');
    } catch (e) {}
    auth.signOut();
    setTimeout(() => window.location.reload(), 80);
  };

  // "Se connecter" from guest mode → clear skip flag, reload to show login.
  const handleShowLogin = () => {
    try { localStorage.removeItem('pantrywise.authSkipped'); } catch (e) {}
    window.location.reload();
  };

  // In standalone mode, content fills viewport — handle safe-area insets HERE
  // (body padding doesn't apply to position:fixed, which is why the bottom tab nav
  //  was getting eaten by the iOS home indicator, looking like a dead-end).
  if (isStandalone) {
    const LoginGate = window.PWAuth && window.PWAuth.LoginGate;
    const appContent = (
      <PantryAppContent
        palette={palette} lang={lang}
        iconStyle={tw.iconStyle} density={tw.density}
        recipeStyle={tw.recipeStyle} dashStyle={tw.dashStyle}
        web={false} topInset={0}
        onChangeLang={onChangeLang}
        showOnboardingTweak={tw.showOnboarding}
        autoAddMissingTweak={tw.autoAddMissing !== false}
        setTweak={setTweak} tweaks={tw}
        authUser={auth.user}
        onSignOut={handleSignOut}
        onShowLogin={handleShowLogin}
        authStatus={auth.status}
      />
    );
    return (
      <div style={{
        position: 'fixed', inset: 0,
        background: palette.bg, color: palette.ink,
        display: 'flex', flexDirection: 'column',
        fontFamily: 'Geist, ui-sans-serif, system-ui, sans-serif',
        paddingTop: 'env(safe-area-inset-top)',
        paddingBottom: 'env(safe-area-inset-bottom)',
        paddingLeft: 'env(safe-area-inset-left)',
        paddingRight: 'env(safe-area-inset-right)',
        boxSizing: 'border-box',
        overflow: 'hidden',
      }}>
        {LoginGate ? (
          <LoginGate auth={auth} palette={palette} lang={lang} onToggleLang={onChangeLang}>
            {appContent}
          </LoginGate>
        ) : appContent}
      </div>
    );
  }

  const content = (
    <PantryAppContent
      palette={palette}
      lang={lang}
      iconStyle={tw.iconStyle}
      density={tw.density}
      recipeStyle={tw.recipeStyle}
      dashStyle={tw.dashStyle}
      web={tw.frame === 'web'}
      topInset={tw.frame === 'iphone' ? 58 : 0}
      onChangeLang={onChangeLang}
      showOnboardingTweak={tw.showOnboarding}
      autoAddMissingTweak={tw.autoAddMissing !== false}
      setTweak={setTweak}
      tweaks={tw}
      authUser={auth.user}
      onSignOut={handleSignOut}
      onShowLogin={handleShowLogin}
      authStatus={auth.status}
    />
  );

  let framed;
  if (tw.frame === 'iphone') {
    framed = (
      <IOSDevice width={402} height={874} dark={palette.isDark}>
        {content}
      </IOSDevice>
    );
  } else if (tw.frame === 'android') {
    framed = (
      <AndroidDevice width={412} height={892} dark={palette.isDark}>
        {content}
      </AndroidDevice>
    );
  } else {
    framed = (
      <ChromeWindow width={1240} height={820} url="pantrywise.app" tabs={[{ title: 'PantryWise — ' + t(lang, 'home') }]}>
        {content}
      </ChromeWindow>
    );
  }

  // Stage background
  return (
    <div style={{
      minHeight: '100vh', width: '100vw',
      background: palette.isDark
        ? 'radial-gradient(circle at 30% 20%, #1A211D 0%, #0B0E0C 70%)'
        : 'radial-gradient(circle at 30% 20%, #F1ECE0 0%, #E5DECD 70%)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      padding: 24, boxSizing: 'border-box',
      fontFamily: 'Geist, ui-sans-serif, system-ui, sans-serif',
    }}>
      {framed}

      <TweaksPanel>
        <TweakSection label={lang === 'fr' ? 'Plateforme' : 'Platform'} />
        <TweakRadio label={lang === 'fr' ? 'Cadre' : 'Frame'} value={tw.frame} options={['iphone', 'android', 'web']} onChange={(v) => setTweak('frame', v)} />
        <TweakRadio label={lang === 'fr' ? 'Langue' : 'Language'} value={tw.lang} options={['fr', 'en']} onChange={(v) => setTweak('lang', v)} />

        <TweakSection label={lang === 'fr' ? 'Palette' : 'Palette'} />
        <TweakColor label={lang === 'fr' ? 'Couleur' : 'Color'}
          value={paletteSwatch(tw.palette)}
          options={[paletteSwatch('sage'), paletteSwatch('terracotta'), paletteSwatch('dark')]}
          onChange={(v) => {
            const key = ['sage','terracotta','dark'].find(k => paletteSwatch(k)[0] === v[0]);
            setTweak('palette', key || 'sage');
          }} />

        <TweakSection label={lang === 'fr' ? 'Style' : 'Style'} />
        <TweakRadio label={lang === 'fr' ? 'Tableau de bord' : 'Dashboard'} value={tw.dashStyle} options={['hero', 'cards']} onChange={(v) => setTweak('dashStyle', v)} />
        <TweakRadio label={lang === 'fr' ? 'Inventaire' : 'Inventory'} value={tw.density} options={['cards', 'compact']} onChange={(v) => setTweak('density', v)} />
        <TweakRadio label={lang === 'fr' ? 'Recettes' : 'Recipes'} value={tw.recipeStyle} options={['standard', 'minimal']} onChange={(v) => setTweak('recipeStyle', v)} />
        <TweakRadio label={lang === 'fr' ? 'Icônes aliments' : 'Food icons'} value={tw.iconStyle} options={['emoji', 'line', 'filled']} onChange={(v) => setTweak('iconStyle', v)} />

        <TweakSection label={lang === 'fr' ? 'Flows' : 'Flows'} />
        <TweakToggle label={lang === 'fr' ? 'Onboarding' : 'Onboarding'} value={tw.showOnboarding} onChange={(v) => setTweak('showOnboarding', v)} />
      </TweaksPanel>
    </div>
  );
}

function paletteSwatch(key) {
  const p = PALETTES[key];
  return [p.primary, p.bg, p.amber];
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
