import React, { useState, useEffect, useMemo, useRef, useLayoutEffect } from "https://esm.sh/react@18.3.1";
import { createRoot } from "https://esm.sh/react-dom@18.3.1/client";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.45.0";
import {
  ChefHat,
  Calendar,
  ShoppingCart,
  History as HistoryIcon,
  Plus,
  X,
  Trash2,
  Pencil,
  Check,
  Copy,
  Share2,
  ChevronLeft,
  ChevronRight,
  ChevronDown,
  ChevronUp,
  Users,
  Utensils,
  Search,
  BookOpen,
  Sparkles,
  ListChecks,
  ArrowRight,
  AlertCircle,
  RefreshCw,
  Info,
  Link2,
  Database,
  Star,
  LogOut,
  LogIn,
  Mail,
  Lock,
  UserPlus,
  MoreVertical,
  Tag,
  TrendingUp,
  HelpCircle,
  Wallet,
  Receipt,
  ArrowRightLeft,
  CheckCircle2,
  Euro,
  Camera,
  Image as ImageIcon,
  Download,
  ZoomIn,
  Undo2,
  Archive,
  MoreHorizontal,
  Coffee,
  Soup,
  Salad,
  Carrot,
  Cookie,
  Cake,
  CupSoda,
  Wheat,
  Clock,
  Eye,
  GripVertical,
} from "https://esm.sh/lucide-react@0.400.0?deps=react@18.3.1";
import { SUPABASE_URL, SUPABASE_KEY } from "./config.js";

// ────────────────────────────────────────────────────────────────────
// Supabase-Client (mit Auth-Session-Persistenz)
// ────────────────────────────────────────────────────────────────────
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY, {
  auth: {
    persistSession: true,
    autoRefreshToken: true,
    storage: window.localStorage,
  },
  realtime: { params: { eventsPerSecond: 2 } },
});

// ────────────────────────────────────────────────────────────────────
// Chefkoch-Integration (über Supabase Edge Function als CORS-Proxy)
// Edge Function: /functions/v1/chefkoch-proxy
//   ?action=search&q=…&offset=…&limit=…
//   ?action=recipe&id=…
// ────────────────────────────────────────────────────────────────────
const CHEFKOCH_PROXY_URL = `${SUPABASE_URL}/functions/v1/chefkoch-proxy`;

async function chefkochFetch(params) {
  // apikey als Query-Param statt Header → verhindert CORS-Preflight.
  // Mit Preflight (Header) wird der OPTIONS-Request vom Supabase-Gateway
  // abgewiesen, bevor er unsere Function erreicht.
  const qs = new URLSearchParams({ ...params, apikey: SUPABASE_KEY }).toString();
  const res = await fetch(`${CHEFKOCH_PROXY_URL}?${qs}`, { method: "GET" });
  if (!res.ok) {
    const text = await res.text().catch(() => "");
    throw new Error(`Chefkoch-Proxy ${res.status}: ${text || res.statusText}`);
  }
  return res.json();
}

function chefkochImageUrl(ck, width = 320, height = 240) {
  const tpl = ck?.imageUrlTemplate || ck?.previewImageUrlTemplate;
  if (!tpl) return null;
  return tpl.replace(/<WIDTH>/g, width).replace(/<HEIGHT>/g, height);
}

function guessCategoryFromChefkoch(ck) {
  const text = `${ck?.title || ""} ${(ck?.tags || []).join(" ")} ${ck?.subtitle || ""} ${(ck?.categories || []).map((c) => c?.title || "").join(" ")}`.toLowerCase();
  if (/frühstück|brunch|müsli|porridge|pancake|waffel/.test(text)) return "breakfast";
  if (/suppe|eintopf/.test(text)) return "soup";
  if (/\bsalat\b/.test(text)) return "salad";
  if (/beilage/.test(text)) return "sidedish";
  if (/dessert|\beis\b|pudding|mousse|tiramisu|nachtisch|crème|creme brûlée/.test(text)) return "dessert";
  if (/kuchen|torte|kekse|gebäck|muffin|\bbacken\b|brot|brötchen|plätzchen/.test(text)) return "baking";
  if (/smoothie|cocktail|getränk|drink|limonade|bowle/.test(text)) return "drink";
  if (/snack|fingerfood|häppchen|dip/.test(text)) return "snack";
  return "main";
}

// Prüft, ob ein Rezept aus Chefkoch importiert wurde
function isFromChefkoch(recipe) {
  if (!recipe) return false;
  const url = recipe.sourceUrl || "";
  const author = recipe.sourceAuthor || "";
  return /chefkoch\.de/i.test(url) || /^Chefkoch\b/i.test(author);
}

// Chefkoch-Zutaten → unser Schema { name, amount, unit }
function mapChefkochIngredients(ckDetail) {
  const groups =
    Array.isArray(ckDetail?.ingredientGroups) && ckDetail.ingredientGroups.length > 0
      ? ckDetail.ingredientGroups
      : [{ header: "", ingredients: ckDetail?.ingredients || [] }];
  const out = [];
  for (const g of groups) {
    const header = (g?.header || "").trim();
    const list = Array.isArray(g?.ingredients) ? g.ingredients : [];
    for (const ing of list) {
      const rawName = (ing?.name || "").trim();
      if (!rawName) continue;
      const usage = (ing?.usageInfo || "").trim();
      const name = usage ? `${rawName}, ${usage}` : rawName;
      out.push({
        name,
        amount: Number(ing?.amount) || 0,
        unit: (ing?.unit || "").trim(),
        ...(header ? { section: header } : {}),
      });
    }
  }
  return out;
}

// Chefkoch-Rezept-Detail → unser Recipe-Schema (ohne id/workspace)
function mapChefkochToRecipe(ckDetail) {
  const instructions = (ckDetail?.instructions || "").trim();
  const owner = ckDetail?.owner?.username || "";
  const rezeptUrl =
    ckDetail?.siteUrl ||
    (ckDetail?.id && ckDetail?.title
      ? `https://www.chefkoch.de/rezepte/${ckDetail.id}/${encodeURIComponent(
          ckDetail.title.replace(/\s+/g, "-")
        )}.html`
      : ckDetail?.id
      ? `https://www.chefkoch.de/rezepte/${ckDetail.id}/`
      : "");
  return {
    name: (ckDetail?.title || "").trim() || "Chefkoch-Rezept",
    basePortions: Math.max(1, Number(ckDetail?.servings) || 2),
    ingredients: mapChefkochIngredients(ckDetail),
    preparation: instructions,
    notes: "",
    sourceUrl: rezeptUrl,
    sourceAuthor: owner ? `Chefkoch / ${owner}` : "Chefkoch",
    recipeCategory: guessCategoryFromChefkoch(ckDetail),
    isPublic: false,
  };
}

// Umwandlung zwischen Supabase-Zeilen (snake_case) und App-Objekten (camelCase)
const recipeFromRow = (r) => ({
  id: r.id,
  name: r.name,
  basePortions: r.base_portions,
  notes: r.notes || "",
  preparation: r.preparation || "",
  sourceUrl: r.source_url || "",
  sourceAuthor: r.source_author || "",
  ingredients: Array.isArray(r.ingredients) ? r.ingredients : [],
  createdBy: r.created_by || "",
  updatedBy: r.updated_by || "",
  createdAt: r.created_at,
  updatedAt: r.updated_at,
  isFavorite: !!r.is_favorite,
  usageCount: r.usage_count || 0,
  lastUsedAt: r.last_used_at,
  recipeCategory: r.recipe_category || "main",
  workspaceId: r.workspace_id,
  isPublic: !!r.is_public,
});
const recipeToRow = (r, workspaceId) => ({
  id: r.id,
  name: r.name,
  base_portions: r.basePortions,
  notes: r.notes || "",
  preparation: r.preparation || "",
  source_url: r.sourceUrl || "",
  source_author: r.sourceAuthor || "",
  ingredients: r.ingredients,
  created_by: r.createdBy || "",
  updated_by: r.updatedBy || "",
  updated_at: new Date().toISOString(),
  is_favorite: !!r.isFavorite,
  usage_count: r.usageCount || 0,
  last_used_at: r.lastUsedAt || null,
  recipe_category: r.recipeCategory || "main",
  workspace_id: workspaceId,
  is_public: !!r.isPublic,
});

const planFromRow = (p) => {
  // Migration: Alt-Daten wurden unter der früheren Logik bereits bei der
  // Tagesauswahl gezählt. Damit wir diese nicht bei einer späteren
  // "Woche bestätigen"-Aktion noch einmal zählen, markieren wir alle
  // bereits vorhandenen Tageseinträge mit einem Rezept als "confirmed".
  const rawDays = p.days || {};
  const days = {};
  for (const [key, day] of Object.entries(rawDays)) {
    if (day && day.recipeId && day.confirmed === undefined) {
      days[key] = { ...day, confirmed: true };
    } else {
      days[key] = day;
    }
  }
  return {
    weekStart: p.week_start,
    days,
    confirmedAt: p.confirmed_at,
    confirmedBy: p.confirmed_by || null,
  };
};
const planToRow = (weekStart, plan, workspaceId) => ({
  week_start: weekStart,
  days: plan.days || {},
  confirmed_at: plan.confirmedAt,
  confirmed_by: plan.confirmedBy || null,
  workspace_id: workspaceId,
});

const categoryFromRow = (c) => ({
  id: c.id,
  name: c.name,
  icon: c.icon || "",
  orderIndex: c.order_index || 0,
});

const db = {
  // ── Workspace (eigener) ──
  async getMyWorkspace() {
    // Über Join der Policy-geschützten Tabellen: Wir lesen zuerst die
    // eigene Mitgliedschaft, dann den Workspace selbst.
    const { data: memberRow, error: mErr } = await supabase
      .from("workspace_members")
      .select("workspace_id, role, joined_at")
      .eq("user_id", (await supabase.auth.getUser()).data.user?.id)
      .maybeSingle();
    if (mErr) throw mErr;
    if (!memberRow) return null;
    const { data: ws, error: wErr } = await supabase
      .from("workspaces")
      .select("id, name, invite_code, owner_id")
      .eq("id", memberRow.workspace_id)
      .maybeSingle();
    if (wErr) throw wErr;
    if (!ws) return null;
    return {
      id: ws.id,
      name: ws.name,
      inviteCode: ws.invite_code,
      ownerId: ws.owner_id,
      role: memberRow.role,
    };
  },
  async createWorkspace(name) {
    const { data, error } = await supabase.rpc("create_workspace", { ws_name: name });
    if (error) throw error;
    return data;
  },
  async joinWorkspace(code) {
    const { data, error } = await supabase.rpc("join_workspace", { code });
    if (error) throw error;
    return data;
  },
  async leaveWorkspace() {
    const { data, error } = await supabase.rpc("leave_workspace");
    if (error) throw error;
    return data;
  },
  async regenerateInvite() {
    const { data, error } = await supabase.rpc("regenerate_invite_code");
    if (error) throw error;
    return data;
  },
  async listMembers() {
    const { data, error } = await supabase.rpc("list_workspace_members");
    if (error) throw error;
    return (data || []).map((r) => ({
      userId: r.user_id,
      email: r.email,
      displayName: r.display_name || "",
      role: r.role,
      joinedAt: r.joined_at,
    }));
  },

  // ── Rezepte (eigener Workspace) ──
  async listRecipes(workspaceId) {
    const { data, error } = await supabase
      .from("recipes")
      .select("*")
      .eq("workspace_id", workspaceId)
      .order("name");
    if (error) throw error;
    return data.map(recipeFromRow);
  },
  async saveRecipe(recipe, workspaceId) {
    const { error } = await supabase
      .from("recipes")
      .upsert(recipeToRow(recipe, workspaceId));
    if (error) throw error;
  },
  async deleteRecipe(id) {
    const { error } = await supabase.from("recipes").delete().eq("id", id);
    if (error) throw error;
  },
  async bumpRecipeUsage(id) {
    const { data: existing } = await supabase
      .from("recipes")
      .select("usage_count")
      .eq("id", id)
      .maybeSingle();
    const newCount = (existing?.usage_count || 0) + 1;
    await supabase
      .from("recipes")
      .update({ usage_count: newCount, last_used_at: new Date().toISOString() })
      .eq("id", id);
  },
  async toggleFavorite(id, nextValue) {
    const { error } = await supabase
      .from("recipes")
      .update({ is_favorite: nextValue })
      .eq("id", id);
    if (error) throw error;
  },
  async togglePublic(id, nextValue) {
    const { error } = await supabase
      .from("recipes")
      .update({ is_public: nextValue })
      .eq("id", id);
    if (error) throw error;
  },

  // ── Entdecken: öffentliche Rezepte ANDERER Workspaces ──
  async listPublicRecipes(excludeWorkspaceId) {
    const { data, error } = await supabase
      .from("recipes")
      .select("*")
      .eq("is_public", true)
      .neq("workspace_id", excludeWorkspaceId)
      .order("name");
    if (error) throw error;
    return data.map(recipeFromRow);
  },

  // ── Pläne ──
  async listPlans(workspaceId) {
    const { data, error } = await supabase
      .from("plans")
      .select("*")
      .eq("workspace_id", workspaceId);
    if (error) throw error;
    const out = {};
    for (const p of data) out[p.week_start] = planFromRow(p);
    return out;
  },
  async savePlan(weekStart, plan, workspaceId) {
    // Composite PK (workspace_id, week_start) → onConflict explizit angeben
    const { error } = await supabase
      .from("plans")
      .upsert(planToRow(weekStart, plan, workspaceId), {
        onConflict: "workspace_id,week_start",
      });
    if (error) throw error;
  },
  async deletePlan(weekStart, workspaceId) {
    const { error } = await supabase
      .from("plans")
      .delete()
      .eq("workspace_id", workspaceId)
      .eq("week_start", weekStart);
    if (error) throw error;
  },
  async cleanUpAfterRecipeDelete(recipeId, workspaceId) {
    const { data, error } = await supabase
      .from("plans")
      .select("*")
      .eq("workspace_id", workspaceId);
    if (error) throw error;
    const updates = [];
    for (const p of data) {
      const days = p.days || {};
      const filtered = {};
      let changed = false;
      for (const [k, d] of Object.entries(days)) {
        if (d?.recipeId === recipeId) changed = true;
        else filtered[k] = d;
      }
      if (changed) updates.push({ ...p, days: filtered });
    }
    if (updates.length > 0) {
      const { error: upErr } = await supabase
        .from("plans")
        .upsert(updates, { onConflict: "workspace_id,week_start" });
      if (upErr) throw upErr;
    }
  },

  // ── Kategorien ──
  async listCategories(workspaceId) {
    const { data, error } = await supabase
      .from("categories")
      .select("*")
      .eq("workspace_id", workspaceId)
      .order("order_index");
    if (error) throw error;
    return data.map(categoryFromRow);
  },
  async listIngredientCategories(workspaceId) {
    const { data, error } = await supabase
      .from("ingredient_categories")
      .select("*")
      .eq("workspace_id", workspaceId);
    if (error) throw error;
    const out = {};
    for (const row of data) {
      out[row.ingredient_name.toLowerCase()] = row.category_id;
    }
    return out;
  },
  async setIngredientCategory(ingredientName, categoryId, workspaceId) {
    const clean = ingredientName.trim();
    if (!clean) return;
    if (categoryId) {
      const { error } = await supabase.from("ingredient_categories").upsert(
        {
          ingredient_name: clean.toLowerCase(),
          category_id: categoryId,
          updated_at: new Date().toISOString(),
          workspace_id: workspaceId,
        },
        { onConflict: "workspace_id,ingredient_name" }
      );
      if (error) throw error;
    } else {
      const { error } = await supabase
        .from("ingredient_categories")
        .delete()
        .eq("workspace_id", workspaceId)
        .eq("ingredient_name", clean.toLowerCase());
      if (error) throw error;
    }
  },

  // ── Einkäufe (Haushaltskasse) ──
  async listPurchases(workspaceId) {
    const { data, error } = await supabase
      .from("purchases")
      .select("*")
      .eq("workspace_id", workspaceId)
      .order("purchased_at", { ascending: false });
    if (error) throw error;
    return data || [];
  },
  async savePurchase(p, workspaceId) {
    const row = {
      ...(p.id ? { id: p.id } : {}),
      workspace_id: workspaceId,
      paid_by: p.paid_by,
      amount_cents: p.amount_cents,
      currency: p.currency || "EUR",
      purchased_at: p.purchased_at || new Date().toISOString(),
      week_starts: p.week_starts || [],
      store: p.store || null,
      note: p.note || null,
      split_mode: p.split_mode || "equal",
      split_data: p.split_data || {},
      excluded_members: p.excluded_members || [],
      receipt_path: p.receipt_path || null,
      updated_at: new Date().toISOString(),
    };
    const { data, error } = await supabase
      .from("purchases")
      .upsert(row)
      .select()
      .maybeSingle();
    if (error) throw error;
    return data;
  },
  async deletePurchase(id) {
    const { error } = await supabase.from("purchases").delete().eq("id", id);
    if (error) throw error;
  },

  // ── Settlements (Ausgleichszahlungen) ──
  async listSettlements(workspaceId) {
    const { data, error } = await supabase
      .from("settlements")
      .select("*")
      .eq("workspace_id", workspaceId)
      .order("settled_at", { ascending: false });
    if (error) throw error;
    return data || [];
  },
  async saveSettlement(s, workspaceId) {
    const row = {
      ...(s.id ? { id: s.id } : {}),
      workspace_id: workspaceId,
      from_user: s.from_user || s.from,
      to_user: s.to_user || s.to,
      amount_cents: s.amount_cents,
      currency: s.currency || "EUR",
      settled_at: s.settled_at || new Date().toISOString(),
      note: s.note || null,
    };
    const { data, error } = await supabase
      .from("settlements")
      .upsert(row)
      .select()
      .maybeSingle();
    if (error) throw error;
    return data;
  },
  async deleteSettlement(id) {
    const { error } = await supabase.from("settlements").delete().eq("id", id);
    if (error) throw error;
  },

  // ── Einkaufslisten-Häkchen (synchronisiert) ──
  // Eintrag mit purchased_at = NULL  → abgehakt (im Wagen)
  // Eintrag mit purchased_at != NULL → gekauft (aus Liste ausgeblendet)
  // Kein Eintrag                     → offen
  async listShoppingChecks(workspaceId) {
    const { data, error } = await supabase
      .from("shopping_checks")
      .select("item_key, purchased_at")
      .eq("workspace_id", workspaceId);
    if (error) throw error;
    const checked = new Set();
    const purchased = new Set();
    for (const row of data || []) {
      if (row.purchased_at) purchased.add(row.item_key);
      else checked.add(row.item_key);
    }
    return { checked, purchased };
  },
  async setShoppingCheck(workspaceId, itemKey, userId) {
    const { error } = await supabase
      .from("shopping_checks")
      .upsert(
        {
          workspace_id: workspaceId,
          item_key: itemKey,
          checked_at: new Date().toISOString(),
          checked_by: userId || null,
          purchased_at: null,
          purchased_by: null,
        },
        { onConflict: "workspace_id,item_key" }
      );
    if (error) throw error;
  },
  async unsetShoppingCheck(workspaceId, itemKey) {
    const { error } = await supabase
      .from("shopping_checks")
      .delete()
      .eq("workspace_id", workspaceId)
      .eq("item_key", itemKey);
    if (error) throw error;
  },
  // Markiert eine Liste abgehakter Items als „gekauft" (aus Liste ausblenden).
  // Items, die noch nicht in der Tabelle sind, werden vorher upserted.
  async markPurchased(workspaceId, itemKeys, userId) {
    if (!Array.isArray(itemKeys) || itemKeys.length === 0) return;
    const now = new Date().toISOString();
    const rows = itemKeys.map((k) => ({
      workspace_id: workspaceId,
      item_key: k,
      checked_at: now,
      checked_by: userId || null,
      purchased_at: now,
      purchased_by: userId || null,
    }));
    const { error } = await supabase
      .from("shopping_checks")
      .upsert(rows, { onConflict: "workspace_id,item_key" });
    if (error) throw error;
  },
  // Item zurück in den Status „offen" — Eintrag wird komplett entfernt
  async unmarkPurchased(workspaceId, itemKey) {
    const { error } = await supabase
      .from("shopping_checks")
      .delete()
      .eq("workspace_id", workspaceId)
      .eq("item_key", itemKey);
    if (error) throw error;
  },
  async clearShoppingChecks(workspaceId, itemKeys = null) {
    let q = supabase
      .from("shopping_checks")
      .delete()
      .eq("workspace_id", workspaceId);
    if (Array.isArray(itemKeys) && itemKeys.length > 0) {
      q = q.in("item_key", itemKeys);
    }
    const { error } = await q;
    if (error) throw error;
  },
  // Optional: alle als „gekauft" markierten Items eines Workspaces entfernen.
  // Nützlich für „Liste komplett zurücksetzen" — aktuell nicht im UI verbaut,
  // aber per DevTools/Console aufrufbar.
  async clearAllPurchased(workspaceId) {
    const { error } = await supabase
      .from("shopping_checks")
      .delete()
      .eq("workspace_id", workspaceId)
      .not("purchased_at", "is", null);
    if (error) throw error;
  },

  // ── Quittungs-Fotos (Supabase Storage) ──
  async uploadReceipt(file, workspaceId) {
    // Path-Schema: {workspace_id}/{uuid}.{ext}
    // Der workspace_id-Prefix wird von den Storage-Policies geprüft.
    const ext = (file.name?.split(".").pop() || "jpg").toLowerCase().slice(0, 5);
    const safeExt = /^[a-z0-9]+$/.test(ext) ? ext : "jpg";
    const fileId =
      typeof crypto !== "undefined" && crypto.randomUUID
        ? crypto.randomUUID()
        : `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
    const path = `${workspaceId}/${fileId}.${safeExt}`;
    const { error } = await supabase.storage
      .from("receipts")
      .upload(path, file, {
        cacheControl: "3600",
        upsert: false,
        contentType: file.type || "image/jpeg",
      });
    if (error) throw error;
    return path;
  },
  async getReceiptSignedUrl(path, expiresInSec = 3600) {
    if (!path) return null;
    const { data, error } = await supabase.storage
      .from("receipts")
      .createSignedUrl(path, expiresInSec);
    if (error) throw error;
    return data?.signedUrl || null;
  },
  async deleteReceipt(path) {
    if (!path) return;
    const { error } = await supabase.storage.from("receipts").remove([path]);
    if (error) throw error;
  },
};

// Lokaler Gerätespeicher (nur noch für optionale Einstellungen)
const localStore = {
  getLastCategory() {
    try {
      return localStorage.getItem("mp-last-category") || "";
    } catch {
      return "";
    }
  },
  setLastCategory(c) {
    try {
      localStorage.setItem("mp-last-category", c);
    } catch {
      /* ignore */
    }
  },
};

// ────────────────────────────────────────────────────────────────────
// Utilities
// ────────────────────────────────────────────────────────────────────
const uid = () => Math.random().toString(36).slice(2, 10) + Date.now().toString(36);

function getInitials(name) {
  if (!name) return "?";
  const parts = name.trim().split(/\s+/).filter(Boolean);
  if (parts.length === 0) return "?";
  if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
  return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
}

function getAvatarColor(name) {
  const s = (name || "?").toString();
  let h = 0;
  for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) | 0;
  const palette = [
    { bg: "#c06a43", fg: "#fdfaf3" },
    { bg: "#3d4a2a", fg: "#f5efe4" },
    { bg: "#a8422a", fg: "#fdfaf3" },
    { bg: "#5e7a4d", fg: "#fdfaf3" },
    { bg: "#7a5a3a", fg: "#fdfaf3" },
    { bg: "#4a6b8a", fg: "#fdfaf3" },
    { bg: "#8a4a6b", fg: "#fdfaf3" },
  ];
  return palette[Math.abs(h) % palette.length];
}

const DAYS = [
  { key: "mon", label: "Montag", short: "Mo" },
  { key: "tue", label: "Dienstag", short: "Di" },
  { key: "wed", label: "Mittwoch", short: "Mi" },
  { key: "thu", label: "Donnerstag", short: "Do" },
  { key: "fri", label: "Freitag", short: "Fr" },
  { key: "sat", label: "Samstag", short: "Sa" },
  { key: "sun", label: "Sonntag", short: "So" },
];

const UNIT_OPTIONS = [
  "g",
  "kg",
  "ml",
  "l",
  "Stück",
  "TL",
  "EL",
  "Prise",
  "Zehe",
  "Bund",
  "Packung",
  "Dose",
  "nach Geschmack",
];

const RECIPE_CATEGORIES = [
  { id: "breakfast", label: "Frühstück",    icon: "🥐", color: "#d89a5c" },
  { id: "main",      label: "Hauptgericht", icon: "🍽️", color: "#c06a43" },
  { id: "soup",      label: "Suppe",        icon: "🍲", color: "#a8422a" },
  { id: "salad",     label: "Salat",        icon: "🥗", color: "#5e7a4d" },
  { id: "sidedish",  label: "Beilage",      icon: "🥔", color: "#7a5a3a" },
  { id: "snack",     label: "Snack",        icon: "🥨", color: "#8a6a3a" },
  { id: "dessert",   label: "Dessert",      icon: "🍰", color: "#8a4a6b" },
  { id: "drink",     label: "Getränk",      icon: "🥤", color: "#4a6b8a" },
  { id: "baking",    label: "Backen",       icon: "🍞", color: "#7a5a3a" },
  { id: "other",     label: "Sonstiges",    icon: "🍴", color: "#6b6358" },
];
const recipeCategoryMap = Object.fromEntries(
  RECIPE_CATEGORIES.map((c) => [c.id, c])
);
const getRecipeCategory = (id) => recipeCategoryMap[id] || recipeCategoryMap.main;

// ────────────────────────────────────────────────────────────────────
// Lucide-Icon-Map pro Kategorie (für die neue Karten-Optik)
// ────────────────────────────────────────────────────────────────────
const RECIPE_CATEGORY_ICON_COMPONENT = {
  breakfast: Coffee,
  main: Utensils,
  soup: Soup,
  salad: Salad,
  sidedish: Carrot,
  snack: Cookie,
  dessert: Cake,
  drink: CupSoda,
  baking: Wheat,
  other: Utensils,
};
const getRecipeCategoryIcon = (id) =>
  RECIPE_CATEGORY_ICON_COMPONENT[id] || RECIPE_CATEGORY_ICON_COMPONENT.main;

// ────────────────────────────────────────────────────────────────────
// Heuristische Schätzung der Kochzeit (in Minuten, gerundet auf 5)
// Basis: 15 Min + 2 Min/Zutat. Kategorie justiert.
// Wenn explizite Zutaten-Zahl 0, fällt auf 20 Min zurück.
// ────────────────────────────────────────────────────────────────────
function estimateCookTime(recipe) {
  if (!recipe) return 20;
  const ingCount = (recipe.ingredients || []).filter((i) => i.name?.trim()).length;
  if (ingCount === 0) return 20;
  let base = 15 + ingCount * 2;
  switch (recipe.recipeCategory) {
    case "salad":   base = Math.max(10, base - 15); break;
    case "soup":    base += 10; break;
    case "baking":  base += 25; break;
    case "dessert": base += 15; break;
    case "snack":   base = Math.max(10, base - 5); break;
    case "drink":   base = Math.max(5, base - 10); break;
    default: break;
  }
  // Auf nächste 5 runden
  return Math.max(5, Math.round(base / 5) * 5);
}

// ────────────────────────────────────────────────────────────────────
// Findet den nächsten geplanten Tag für ein Rezept (heute oder später)
// Rückgabe: { date, dayKey, weekKey } | null
// ────────────────────────────────────────────────────────────────────
function getNextPlannedDay(recipeId, plans) {
  if (!recipeId || !plans) return null;
  const today = new Date();
  today.setHours(0, 0, 0, 0);
  const candidates = [];
  for (const weekKey of Object.keys(plans)) {
    const plan = plans[weekKey];
    if (!plan?.days) continue;
    const [yy, mm, dd] = weekKey.split("-").map(Number);
    if (!yy) continue;
    const monday = new Date(yy, mm - 1, dd);
    for (const dayKey of Object.keys(plan.days)) {
      const day = plan.days[dayKey];
      if (day?.recipeId !== recipeId) continue;
      const idx = DAYS.findIndex((d) => d.key === dayKey);
      if (idx < 0) continue;
      const date = addDays(monday, idx);
      if (date >= today) candidates.push({ date, dayKey, weekKey });
    }
  }
  candidates.sort((a, b) => a.date - b.date);
  return candidates[0] || null;
}

// Formatiert den nächsten geplanten Tag als „heute" / „morgen" / „Mi., 30. Apr"
function formatPlannedFor(dateOrEntry) {
  const date = dateOrEntry instanceof Date ? dateOrEntry : dateOrEntry?.date;
  if (!date) return "";
  const today = new Date(); today.setHours(0, 0, 0, 0);
  const target = new Date(date); target.setHours(0, 0, 0, 0);
  const diff = Math.round((target - today) / 86400000);
  if (diff === 0) return "heute";
  if (diff === 1) return "morgen";
  if (diff < 7) return DAYS[(target.getDay() + 6) % 7]?.label || "";
  return target.toLocaleDateString("de-DE", { weekday: "short", day: "numeric", month: "short" });
}

// Findet, wann ein Rezept zuletzt bestätigt gekocht wurde (für „vor X Wochen")
function getLastCookedDate(recipeId, plans) {
  if (!recipeId || !plans) return null;
  const today = new Date(); today.setHours(0, 0, 0, 0);
  const candidates = [];
  for (const weekKey of Object.keys(plans)) {
    const plan = plans[weekKey];
    if (!plan?.days || !plan.confirmedAt) continue;
    const [yy, mm, dd] = weekKey.split("-").map(Number);
    if (!yy) continue;
    const monday = new Date(yy, mm - 1, dd);
    for (const dayKey of Object.keys(plan.days)) {
      const day = plan.days[dayKey];
      if (day?.recipeId !== recipeId) continue;
      const idx = DAYS.findIndex((d) => d.key === dayKey);
      if (idx < 0) continue;
      const date = addDays(monday, idx);
      if (date < today) candidates.push(date);
    }
  }
  if (!candidates.length) return null;
  candidates.sort((a, b) => b - a);
  return candidates[0];
}

// Formatiert „zuletzt vor X Wochen" / „vor 3 Tagen" / „vor 4 Monaten"
function formatLastCooked(date) {
  if (!date) return "";
  const today = new Date(); today.setHours(0, 0, 0, 0);
  const diff = Math.round((today - date) / 86400000);
  if (diff < 1) return "heute gekocht";
  if (diff === 1) return "gestern gekocht";
  if (diff < 7) return `vor ${diff} Tagen`;
  if (diff < 30) return `vor ${Math.round(diff / 7)} Woche${Math.round(diff / 7) === 1 ? "" : "n"}`;
  if (diff < 365) return `vor ${Math.round(diff / 30)} Monat${Math.round(diff / 30) === 1 ? "" : "en"}`;
  return `vor ${Math.round(diff / 365)} Jahr${Math.round(diff / 365) === 1 ? "" : "en"}`;
}

// Quick-Tags pro Tag im Wochenplan (kontextuelle Markierungen)
const DAY_TAGS = [
  { id: "sport",   label: "Sport-Tag", icon: "🏃", color: "#5e7a4d", hint: "Schnell oder sättigend" },
  { id: "guests",  label: "Gäste",      icon: "👥", color: "#c06a43", hint: "Größere Portion / Vorzeigerezept" },
  { id: "late",    label: "Spät",       icon: "🌙", color: "#4a6b8a", hint: "Schnelles Essen am späten Abend" },
  { id: "veggie",  label: "Veggie",     icon: "🌱", color: "#5e7a4d", hint: "Vegetarisch / vegan" },
  { id: "leftover",label: "Reste",      icon: "🥡", color: "#8a6a3a", hint: "Aus Restem zubereiten" },
  { id: "meal-prep",label: "Vorkochen", icon: "🍱", color: "#7a5a3a", hint: "Für mehrere Tage vorkochen" },
];
const dayTagMap = Object.fromEntries(DAY_TAGS.map((t) => [t.id, t]));

// ────────────────────────────────────────────────────────────────────
// Schnell-Essen-Erkennung
// Heuristik: ≤ 6 Zutaten ODER leere Anleitung
// Manuell überschreibbar: "schnell", "quick" oder 🚀-Emoji im notes-Feld
// ────────────────────────────────────────────────────────────────────
function isQuickMeal(recipe) {
  if (!recipe) return false;
  const notes = (recipe.notes || "").toLowerCase();
  if (/🚀|\bschnell\b|\bquick\b|\bfix\b/.test(notes)) return true;
  // Heuristik
  const ingCount = (recipe.ingredients || []).filter((i) => i.name?.trim()).length;
  const hasPrep = !!(recipe.preparation || "").trim();
  if (ingCount > 0 && ingCount <= 6) return true;
  if (!hasPrep && ingCount <= 8) return true;
  return false;
}

// ────────────────────────────────────────────────────────────────────
// Rezept-Rotations-Analyse
// Zählt, wie oft ein Rezept in einem Zeitfenster geplant wurde.
// Window: aktuelle Woche + 2 vergangene Wochen (≈ 14-21 Tage Sichtfeld)
// ────────────────────────────────────────────────────────────────────
function getRecipeRotationCount(recipeId, plans, weekMonday) {
  if (!recipeId || !plans || !weekMonday) return 0;
  const cutoff = toISODate(addDays(weekMonday, -14));
  const limit = toISODate(addDays(weekMonday, 7));
  let count = 0;
  for (const [weekKey, p] of Object.entries(plans)) {
    if (weekKey < cutoff || weekKey >= limit) continue;
    for (const day of Object.values(p?.days || {})) {
      if (day?.recipeId === recipeId) count++;
    }
  }
  return count;
}

// Analysiert die aktuelle Woche: welche Kategorien dominieren bereits?
function getWeekCategoryCounts(plan, recipes) {
  const counts = {};
  for (const day of Object.values(plan?.days || {})) {
    if (!day?.recipeId) continue;
    const r =
      recipes.find((x) => x.id === day.recipeId) ||
      (day.recipeSnapshot ? { recipeCategory: day.recipeSnapshot.recipeCategory } : null);
    const cat = r?.recipeCategory || "main";
    counts[cat] = (counts[cat] || 0) + 1;
  }
  return counts;
}

// Sammelt Zutatennamen aus dem Vortag (für Reste-Logik)
function getIngredientsFromPrevDay(plan, dayKey, recipes) {
  const dayIndex = DAYS.findIndex((d) => d.key === dayKey);
  if (dayIndex <= 0) return new Set();
  const prevKey = DAYS[dayIndex - 1].key;
  const prev = plan?.days?.[prevKey];
  if (!prev?.recipeId) return new Set();
  const r =
    recipes.find((x) => x.id === prev.recipeId) ||
    (prev.recipeSnapshot
      ? { ingredients: prev.recipeSnapshot.ingredients || [] }
      : null);
  if (!r) return new Set();
  const set = new Set();
  for (const ing of r.ingredients || []) {
    const k = canonicalBaseKey(ing.name);
    if (k) set.add(k);
  }
  return set;
}

// Wochentags-Muster aus dem Verlauf (alle Pläne)
// Zählt, wie oft Rezept-Kategorien an einem bestimmten Wochentag vorkamen
function getWeekdayPattern(dayKey, plans, recipes) {
  const counts = {};
  for (const p of Object.values(plans || {})) {
    const d = p?.days?.[dayKey];
    if (!d?.recipeId) continue;
    const r =
      recipes.find((x) => x.id === d.recipeId) ||
      (d.recipeSnapshot ? { recipeCategory: d.recipeSnapshot.recipeCategory } : null);
    const cat = r?.recipeCategory || "main";
    counts[cat] = (counts[cat] || 0) + 1;
  }
  return counts;
}

// ────────────────────────────────────────────────────────────────────
// Smart-Vorschläge für einen leeren Tag
// Berücksichtigt: Wochenbalance, Reste-Verwertung, Wochentags-Muster,
// Quick-Meal-Filter (optional)
// Returnt: Top-3 Rezepte mit Score & 1-Zeilen-Begründung
// ────────────────────────────────────────────────────────────────────
function getSuggestionsForDay({
  dayKey,
  plan,
  plans,
  recipes,
  weekMonday,
  quickOnly = false,
}) {
  if (!recipes || recipes.length === 0) return [];
  const weekCats = getWeekCategoryCounts(plan, recipes);
  const prevIngs = getIngredientsFromPrevDay(plan, dayKey, recipes);
  const weekdayPattern = getWeekdayPattern(dayKey, plans, recipes);
  const dominantCat = Object.entries(weekCats).sort((a, b) => b[1] - a[1])[0];
  const isDominating = dominantCat && dominantCat[1] >= 3;

  // Bereits in der aktuellen Woche eingeplante Rezept-IDs (für Strafe)
  const plannedThisWeek = new Set();
  for (const d of Object.values(plan?.days || {})) {
    if (d?.recipeId) plannedThisWeek.add(d.recipeId);
  }

  const candidates = recipes
    .filter((r) => !quickOnly || isQuickMeal(r))
    .map((r) => {
      let score = 0;
      const reasons = [];

      // Reste-Bonus: Überlappung mit Vortag
      if (prevIngs.size > 0) {
        let overlap = 0;
        for (const ing of r.ingredients || []) {
          const k = canonicalBaseKey(ing.name);
          if (k && prevIngs.has(k)) overlap++;
        }
        if (overlap >= 2) {
          score += 3;
          reasons.push(`nutzt Reste vom Vortag (${overlap} Zutaten)`);
        }
      }

      // Wochentags-Muster
      const cat = r.recipeCategory || "main";
      const weekdayHits = weekdayPattern[cat] || 0;
      if (weekdayHits >= 2) {
        score += 2;
        const dayLabel = DAYS.find((d) => d.key === dayKey)?.label || dayKey;
        reasons.push(`${dayLabel}-Klassiker bei dir`);
      }

      // Balance: belohne Kategorien, die diese Woche fehlen
      if (!weekCats[cat]) {
        score += 1.5;
        if (reasons.length === 0) reasons.push("bringt Abwechslung in die Woche");
      }

      // Strafe: Kategorie dominiert bereits
      if (isDominating && cat === dominantCat[0]) {
        score -= 2;
      }

      // Strafe: Rezept ist diese Woche schon dabei
      if (plannedThisWeek.has(r.id)) {
        score -= 3;
      }

      // Schwacher Bonus: Favorit
      if (r.isFavorite) score += 0.5;

      // Schwacher Bonus: häufig genutzt
      if ((r.usageCount || 0) > 0) score += Math.min(0.5, r.usageCount * 0.05);

      return { recipe: r, score, reason: reasons[0] || "passt zu deiner Woche" };
    })
    .filter((c) => c.score > 0)
    .sort((a, b) => b.score - a.score)
    .slice(0, 3);

  return candidates;
}

function getMonday(date = new Date()) {
  const d = new Date(date);
  d.setHours(0, 0, 0, 0);
  const day = d.getDay();
  const diff = day === 0 ? -6 : 1 - day;
  d.setDate(d.getDate() + diff);
  return d;
}

function addDays(date, n) {
  const d = new Date(date);
  d.setDate(d.getDate() + n);
  return d;
}

function toISODate(date) {
  const d = new Date(date);
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, "0");
  const day = String(d.getDate()).padStart(2, "0");
  return `${y}-${m}-${day}`;
}

function formatDateShort(date) {
  const d = new Date(date);
  return d.toLocaleDateString("de-DE", { day: "2-digit", month: "2-digit" });
}

function formatDateLong(date) {
  const d = new Date(date);
  return d.toLocaleDateString("de-DE", {
    weekday: "long",
    day: "2-digit",
    month: "long",
    year: "numeric",
  });
}

function formatWeekRange(monday) {
  const start = formatDateShort(monday);
  const end = formatDateShort(addDays(monday, 6));
  return `${start} – ${end}`;
}

function getWeekNumber(date) {
  const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
  const dayNum = d.getUTCDay() || 7;
  d.setUTCDate(d.getUTCDate() + 4 - dayNum);
  const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
  return Math.ceil(((d - yearStart) / 86400000 + 1) / 7);
}

function toBase(amount, unit) {
  if (unit === "kg") return { amount: amount * 1000, unit: "g" };
  if (unit === "l") return { amount: amount * 1000, unit: "ml" };
  return { amount, unit };
}

function fromBase(amount, baseUnit) {
  if (baseUnit === "g" && amount >= 1000) {
    const kg = amount / 1000;
    return { amount: Number(kg.toFixed(2)), unit: "kg" };
  }
  if (baseUnit === "ml" && amount >= 1000) {
    const l = amount / 1000;
    return { amount: Number(l.toFixed(2)), unit: "l" };
  }
  return { amount: Number(amount.toFixed(2)), unit: baseUnit };
}

function prettyNumber(n) {
  if (typeof n !== "number") return n;
  if (Number.isInteger(n)) return String(n);
  return String(Number(n.toFixed(2))).replace(".", ",");
}

// Praxistaugliches Runden für die Einkaufsliste:
// - zählbare Einheiten (Stück, Zehe, Schote, Bund, …): aufrunden auf ganze Zahl
// - g/ml: auf 5er-Schritte runden (unter 5 → 0,5er)
// - EL/TL: auf 0,5-Schritte runden
// - sonst: unverändert
function smartRoundAmount(amount, unit) {
  if (typeof amount !== "number" || !isFinite(amount)) return amount;
  const u = (unit || "").toLowerCase();
  const countable = [
    "stück", "m.-große", "kl.-große", "gr.-große",
    "blätter", "blatt", "handvoll", "zehe", "schote",
    "bund", "kopf", "scheibe", "dose", "packung",
    "prise", "zweig", "rispe", "stange",
  ];
  if (countable.some((p) => u.includes(p))) return Math.ceil(amount);
  if (u === "g" || u === "ml") {
    if (amount < 5) return Math.round(amount * 2) / 2;
    return Math.round(amount / 5) * 5;
  }
  if (u === "el" || u === "tl") return Math.round(amount * 2) / 2;
  return amount;
}

function buildShoppingList(planOrPlans, recipes, ingredientCategories, categories) {
  // Akzeptiert entweder einen einzelnen Plan oder ein Array von Plänen,
  // damit mehrere Wochen in eine kombinierte Einkaufsliste aggregiert werden können.
  const plans = Array.isArray(planOrPlans)
    ? planOrPlans.filter(Boolean)
    : planOrPlans
    ? [planOrPlans]
    : [];
  const map = new Map();
  for (const plan of plans) {
    for (const day of DAYS) {
      const entry = plan?.days?.[day.key];
      if (!entry?.recipeId) continue;
      // Erst in den eigenen Rezepten suchen; wenn nicht gefunden (z.B. weil
      // das Rezept öffentlich aus einem fremden Workspace stammt) auf den
      // im Tages-Eintrag gespeicherten Snapshot zurückfallen.
      const recipe =
        recipes.find((r) => r.id === entry.recipeId) ||
        (entry.recipeSnapshot
          ? {
              id: entry.recipeId,
              name: entry.recipeSnapshot.name,
              basePortions: entry.recipeSnapshot.basePortions || 2,
              ingredients: entry.recipeSnapshot.ingredients || [],
            }
          : null);
      if (!recipe) continue;
      const factor = (entry.portions || recipe.basePortions) / recipe.basePortions;
      for (const ing of recipe.ingredients) {
        const rawName = (ing.name || "").trim();
        if (!rawName) continue;
        const cleanName = displayIngredientName(rawName);
        if (!cleanName) continue;
        const scaledAmount = (Number(ing.amount) || 0) * factor;
        const unit = ing.unit || "";
        const base = toBase(scaledAmount, unit);
        const aggKey = shoppingAggKey(rawName);
        const key = `${aggKey}|${base.unit}`;
        if (!map.has(key)) {
          map.set(key, {
            name: cleanName,
            amount: 0,
            baseUnit: base.unit,
            sources: [],
          });
        } else {
          // Bereits vorhanden: kürzere/sauberere Schreibweise als Display behalten
          const existing = map.get(key);
          const existingHasParens = /\(/.test(existing.name);
          const newHasParens = /\(/.test(cleanName);
          if (existingHasParens && !newHasParens) {
            existing.name = cleanName;
          } else if (existingHasParens === newHasParens && cleanName.length < existing.name.length) {
            existing.name = cleanName;
          }
        }
        map.get(key).amount += base.amount;
        map.get(key).sources.push({
          recipeId: recipe.id,
          recipeName: recipe.name,
          dayKey: day.key,
          dayShort: day.short,
          weekStart: plan?.weekStart || null,
        });
      }
    }
  }

  const list = [];
  for (const it of map.values()) {
    let display;
    if (it.baseUnit === "nach Geschmack") {
      display = { name: it.name, amount: null, unit: "nach Geschmack" };
    } else if (it.baseUnit) {
      const { amount, unit } = fromBase(it.amount, it.baseUnit);
      display = { name: it.name, amount, unit };
    } else if (it.amount > 0) {
      display = { name: it.name, amount: it.amount, unit: "" };
    } else {
      display = { name: it.name, amount: null, unit: "" };
    }
    const manualCatId =
      ingredientCategories?.[it.name.toLowerCase()] ||
      ingredientCategories?.[it.name.trim().toLowerCase()] ||
      null;
    // Fallback: automatisch erkannte Kategorie (wenn keine manuelle gesetzt ist)
    const catId = manualCatId || autoGuessCategoryId(it.name, categories);
    display.categoryId = catId;
    display.autoCategorized = !manualCatId && !!catId;
    // Quell-Rezepte für dieses Item dedupen (gleiche Rezept-Instanz an mehreren
    // Tagen → nur einmal anzeigen). Reihenfolge: Auftreten erhalten.
    const seen = new Set();
    display.sources = it.sources.filter((s) => {
      const k = `${s.recipeId}|${s.weekStart || ""}|${s.dayKey}`;
      if (seen.has(k)) return false;
      seen.add(k);
      return true;
    });
    list.push(display);
  }

  const categoryMap = new Map();
  if (Array.isArray(categories)) {
    for (const c of categories) categoryMap.set(c.id, c);
  }
  const groups = {};
  for (const it of list) {
    const key = it.categoryId || "__uncat";
    if (!groups[key]) groups[key] = [];
    groups[key].push(it);
  }
  const out = [];
  const sortedCats = [...(categories || [])].sort((a, b) => a.orderIndex - b.orderIndex);
  for (const c of sortedCats) {
    if (groups[c.id]?.length) {
      out.push({
        category: c,
        items: groups[c.id].sort((a, b) => a.name.localeCompare(b.name, "de")),
      });
    }
  }
  if (groups["__uncat"]?.length) {
    out.push({
      category: null,
      items: groups["__uncat"].sort((a, b) => a.name.localeCompare(b.name, "de")),
    });
  }
  return out;
}

function formatShoppingListText(groups) {
  const lines = [];
  for (const g of groups) {
    if (g.category) {
      if (lines.length > 0) lines.push("");
      lines.push(`— ${g.category.icon ? g.category.icon + " " : ""}${g.category.name} —`);
    } else if (lines.length > 0) {
      lines.push("");
      lines.push("— Sonstiges —");
    }
    for (const i of g.items) {
      if (i.amount == null) lines.push(`${i.name} (${i.unit || "nach Bedarf"})`);
      else lines.push(`${prettyNumber(smartRoundAmount(i.amount, i.unit))} ${i.unit} ${i.name}`.trim());
    }
  }
  return lines.join("\n");
}

function flattenGroups(groups) {
  const out = [];
  for (const g of groups) for (const it of g.items) out.push(it);
  return out;
}

// ────────────────────────────────────────────────────────────────────
// Reste-Koch: Match-Logik
// ────────────────────────────────────────────────────────────────────

const STAPLE_INGREDIENTS = [
  "salz",
  "pfeffer",
  "öl",
  "olivenöl",
  "sonnenblumenöl",
  "wasser",
  "butter",
  "zucker",
  "mehl",
  "essig",
];

function normalizeIngredientName(name) {
  let n = (name || "").toLowerCase().trim();
  n = n.replace(/\([^)]*\)/g, "").trim();
  n = n.replace(/[,;:]/g, " ").replace(/\s+/g, " ").trim();
  return n;
}

// Reduziert Wörter auf gemeinsamen Stamm (deutsche Pluralformen, Umlaute).
// Grob, aber ausreichend für Zutaten-Matching: "Tomaten" ≈ "Tomate",
// "Zwiebeln" ≈ "Zwiebel", "Möhren" ≈ "Moehren".
function stemIngredient(name) {
  let n = normalizeIngredientName(name);
  n = n
    .replace(/ä/g, "ae")
    .replace(/ö/g, "oe")
    .replace(/ü/g, "ue")
    .replace(/ß/g, "ss");
  // Gängige dt. Plural-/Flexions-Endungen abschneiden (für Wörter ≥ 5 Zeichen)
  if (n.length >= 6) {
    n = n.replace(/(nen|innen|ungen|chen|lein)$/i, "");
  }
  if (n.length >= 5) {
    n = n.replace(/(en|er|es|em)$/i, "");
  }
  if (n.length >= 4) {
    n = n.replace(/[ns]$/i, "");
  }
  // "Tomate" → "tomat", damit Singular/Plural ("Tomate"/"Tomaten") fusionieren
  if (n.length >= 5) {
    n = n.replace(/e$/i, "");
  }
  return n;
}

// Entfernt das Section-Header-Präfix wie "[Außerdem:]" oder "[Für das Pesto]"
// am Anfang eines Zutatennamens. Lässt den Rest unverändert (Originalschreibung).
function stripSectionPrefix(name) {
  return (name || "").replace(/^\s*\[[^\]]+\]\s*/, "").trim();
}

// Anzeige-tauglicher Name: Section-Präfix entfernen, doppelte/leere
// Kommas und Mehrfach-Spaces bereinigen. Behebt Anzeigefehler wie
// "Olivenöl, , kalt gepresstes" → "Olivenöl, kalt gepresstes".
function displayIngredientName(name) {
  return stripSectionPrefix(name)
    .replace(/,\s*(?=,)/g, "")   // doppelte/mehrfache Kommas zusammenfassen
    .replace(/\s*,\s*$/, "")      // Komma am Ende entfernen
    .replace(/^\s*,\s*/, "")      // Komma am Anfang entfernen
    .replace(/\s{2,}/g, " ")      // Mehrfach-Spaces
    .trim();
}

// Kanonischer "Basis"-Schlüssel für Gruppierung in Reste-Koch.
// Schritte: Section-Präfix entfernen → lowercase → Klammerinhalte raus →
// nur Teil vor erstem Komma/Semikolon/Doppelpunkt → stemmen.
// "Tomate", "Tomate(n)", "Tomaten", "[Außerdem:] Tomaten" → gleicher Key.
// "Brokkoli", "Brokkoli, TK", "Brokkoli, küchenfertig gewogen" → gleicher Key.
function canonicalBaseKey(name) {
  let n = stripSectionPrefix(name).toLowerCase();
  n = n.replace(/\([^)]*\)/g, " ");
  n = n.split(/[,;:]/)[0];
  n = n.replace(/\s+/g, " ").trim();
  return n ? stemIngredient(n) : "";
}

// Aggregations-Schlüssel für die Einkaufsliste: behält den Qualifier (Komma-
// Suffix), damit "Brokkoli" und "Brokkoli, TK" getrennt bleiben, aber rein
// flexionale/Pluralvarianten zusammenfallen ("Tomate" + "Tomaten" → 1 Eintrag).
function shoppingAggKey(name) {
  let n = stripSectionPrefix(name).toLowerCase();
  n = n.replace(/\([^)]*\)/g, " ");
  n = n.replace(/\s+/g, " ").trim();
  const parts = n
    .split(/[,;]/)
    .map((p) => p.replace(/\s+/g, " ").trim())
    .filter(Boolean);
  if (parts.length === 0) return "";
  const head = stemIngredient(parts[0]);
  const tail = parts.slice(1).join(",");
  return tail ? `${head}|${tail}` : head;
}

// Damerau-Levenshtein (mit Transposition). Klein & schnell.
function editDistance(a, b) {
  if (a === b) return 0;
  const la = a.length;
  const lb = b.length;
  if (la === 0) return lb;
  if (lb === 0) return la;
  const dp = Array.from({ length: la + 1 }, () => new Array(lb + 1).fill(0));
  for (let i = 0; i <= la; i++) dp[i][0] = i;
  for (let j = 0; j <= lb; j++) dp[0][j] = j;
  for (let i = 1; i <= la; i++) {
    for (let j = 1; j <= lb; j++) {
      const cost = a[i - 1] === b[j - 1] ? 0 : 1;
      dp[i][j] = Math.min(
        dp[i - 1][j] + 1,
        dp[i][j - 1] + 1,
        dp[i - 1][j - 1] + cost
      );
      if (
        i > 1 &&
        j > 1 &&
        a[i - 1] === b[j - 2] &&
        a[i - 2] === b[j - 1]
      ) {
        dp[i][j] = Math.min(dp[i][j], dp[i - 2][j - 2] + 1);
      }
    }
  }
  return dp[la][lb];
}

// Ähnlichkeit 0..1 auf Basis Edit-Distance gegen Länge
function similarity(a, b) {
  if (!a || !b) return 0;
  const max = Math.max(a.length, b.length);
  return 1 - editDistance(a, b) / max;
}

function ingredientMatches(a, b) {
  const na = normalizeIngredientName(a);
  const nb = normalizeIngredientName(b);
  if (!na || !nb) return false;
  if (na === nb) return true;
  if (na.length >= 4 && nb.length >= 4) {
    if (na.startsWith(nb) || nb.startsWith(na)) return true;
  }
  if (na.length >= 5 && nb.includes(na)) return true;
  if (nb.length >= 5 && na.includes(nb)) return true;
  // Fuzzy: Stamm-Vergleich + Edit-Distance
  const sa = stemIngredient(a);
  const sb = stemIngredient(b);
  if (sa && sb && sa === sb) return true;
  if (sa.length >= 4 && sb.length >= 4) {
    const maxLen = Math.max(sa.length, sb.length);
    const dist = editDistance(sa, sb);
    // bis 7 Zeichen: 1 Fehler erlaubt, darüber: 2
    const threshold = maxLen <= 7 ? 1 : 2;
    if (dist <= threshold) return true;
  }
  return false;
}

// Findet aus einer Liste existierender Namen den ähnlichsten Treffer
// (oder null, wenn nichts ausreichend ähnlich ist).
function findSimilarIngredient(query, existingNames) {
  const q = stemIngredient(query);
  if (!q || q.length < 3) return null;
  let best = null;
  let bestSim = 0;
  for (const name of existingNames || []) {
    const sim = similarity(q, stemIngredient(name));
    if (sim > bestSim) {
      bestSim = sim;
      best = name;
    }
  }
  // ≥ 0.8 = eindeutig dasselbe mit Tippfehler oder Flexion
  return bestSim >= 0.8 ? best : null;
}

// ─────────────────────────────────────────────────────────────
// Auto-Kategorisierung von Zutaten (dt. Standard-Supermarkt-Rubriken)
// ─────────────────────────────────────────────────────────────
// Der Value ist ein "Hint" (Kategorie-Schlagwort). Beim Shoppinglisten-
// Aufbau wird dann versucht, eine benutzerdefinierte Category zu finden,
// deren Name dieses Schlagwort enthält. Wenn keine passt, bleibt die
// Zutat unkategorisiert (wie bisher).
const INGREDIENT_CATEGORY_HINTS = {
  // Obst & Gemüse
  obst:         ["apfel","birne","banane","orange","mandarine","clementine","zitrone","limette","kiwi","trauben","weintrauben","erdbeere","himbeere","heidelbeere","blaubeere","brombeere","mango","ananas","pfirsich","nektarine","aprikose","pflaume","kirsche","melone","wassermelone","honigmelone","granatapfel","feige","dattel","rosine","cranberry","johannisbeere","stachelbeere","rhabarber"],
  gemuese:      ["tomate","gurke","paprika","zucchini","aubergine","brokkoli","blumenkohl","karfiol","kohlrabi","kohl","weisskohl","rotkohl","wirsing","rosenkohl","spinat","mangold","feldsalat","kopfsalat","eisbergsalat","rucola","endivie","chicoree","radicchio","salat","karotten","möhren","mohre","sellerie","lauch","porree","fenchel","spargel","pak choi","bohnen","erbsen","kichererbsen","linsen","süßkartoffel","kartoffel","kartoffeln","pilze","champignon","steinpilz","pfifferling","artischocke","avocado","oliven","mais","radieschen","rettich","rote bete","rote rübe","pastinake","petersilie","schnittlauch","basilikum","thymian","rosmarin","oregano","dill","minze","koriander","ingwer","knoblauch","zwiebel","schalotte","lauchzwiebel","frühlingszwiebel","chili","peperoni","kürbis","butternut","hokkaido"],

  // Milch & Eier
  milch:        ["milch","h-milch","hafermilch","sojamilch","mandelmilch","reismilch","kokosmilch","sahne","schlagsahne","schmand","crème fraîche","creme fraiche","joghurt","jogurt","griechischer joghurt","quark","buttermilch","kefir","skyr","butter","margarine","käse","kaese","gouda","emmentaler","mozzarella","parmesan","pecorino","feta","hirtenkäse","hirtenkaese","ricotta","frischkäse","frischkaese","camembert","brie","bergkäse","bergkaese","harzer","raclette","blauschimmel","gorgonzola","roquefort","eier","ei","eigelb","eiklar","eiweiss"],

  // Fleisch & Fisch
  fleisch:      ["hackfleisch","rinderhack","putenhack","hähnchen","haehnchen","huhn","pute","truthahn","schnitzel","steak","filet","gulasch","rouladen","kotelett","braten","rind","rindfleisch","schwein","schweinefleisch","lamm","kalb","ente","gans","wurst","salami","schinken","speck","bacon","chorizo","bratwurst","wiener","frankfurter","leberwurst","mettwurst"],
  fisch:        ["lachs","thunfisch","forelle","kabeljau","seelachs","scholle","hering","makrele","sardine","sardelle","anchovis","garnele","shrimps","scampi","tintenfisch","krabbe","muschel","fisch","räucherlachs","raeucherlachs"],

  // Backwaren & Getreide
  backwaren:    ["brot","brötchen","broetchen","toast","baguette","ciabatta","laugenbrezel","brezel","semmel","croissant","kuchen","torte","muffin","kekse","zwieback","knäckebrot","knaeckebrot","pita","wrap","tortilla","fladenbrot"],
  getreide:     ["mehl","weizenmehl","roggenmehl","dinkelmehl","vollkornmehl","haferflocken","müsli","muesli","cornflakes","reis","basmati","jasmin","risotto","naturreis","wildreis","nudeln","pasta","spaghetti","penne","fusilli","makkaroni","lasagne","bandnudeln","spätzle","spaetzle","couscous","bulgur","quinoa","polenta","grieß","griess","graupen","hirse","buchweizen"],

  // Konserven, Öle, Gewürze
  konserven:    ["tomaten aus der dose","dosentomaten","passierte tomaten","tomatenmark","mais aus der dose","bohnen aus der dose","kichererbsen aus der dose","linsen aus der dose","thunfisch aus der dose","kokosmilch dose","ketchup","senf","mayonnaise","mayo","remoulade","barbecue","bbq","sojasauce","sojasoße","worcestersauce"],
  oele:         ["olivenöl","olivenoel","rapsöl","rapsoel","sonnenblumenöl","sonnenblumenoel","sesamöl","sesamoel","kokosöl","kokosoel","butterschmalz","ghee","essig","balsamico","balsamessig","weißweinessig","weissweinessig","rotweinessig","apfelessig"],
  gewuerze:     ["salz","meersalz","pfeffer","paprikapulver","curry","kurkuma","kreuzkümmel","kreuzkuemmel","kümmel","kuemmel","zimt","muskat","muskatnuss","nelken","lorbeer","lorbeerblatt","vanille","vanillezucker","zucker","puderzucker","brauner zucker","rohrzucker","honig","sirup","ahornsirup","agavendicksaft","backpulver","natron","hefe","trockenhefe"],

  // Getränke
  getraenke:    ["wasser","mineralwasser","sprudel","saft","apfelsaft","orangensaft","tomatensaft","cola","limonade","tee","schwarztee","grüntee","gruentee","kräutertee","kraeutertee","kaffee","espresso","bier","wein","rotwein","weißwein","weisswein","sekt","prosecco","champagner"],

  // Tiefkühl
  tiefkuehl:    ["tk-gemüse","tk-gemuese","tk-himbeeren","tk-erdbeeren","tk-beeren","tk-pizza","tk-spinat","tk-brokkoli","tk-erbsen","tk-kräuter","tk-kraeuter","eis","speiseeis","gefrorene","tiefkühl","tiefkuehl"],

  // Süßes & Snacks
  suess:        ["schokolade","kakao","nutella","nuss-nougat","bonbons","gummibärchen","chips","salzstangen","nüsse","nuesse","mandeln","walnüsse","walnuesse","haselnüsse","haselnuesse","cashew","cashewnüsse","cashewnuesse","erdnüsse","erdnuesse","pistazien","pinienkerne","sonnenblumenkerne","kürbiskerne","kuerbiskerne","sesam","chiasamen","leinsamen","kokosraspel","kokosflocken"],
};

// Invertiertes Lookup: Zutat → Hint
const INGREDIENT_HINT_LOOKUP = (() => {
  const map = new Map();
  for (const [hint, names] of Object.entries(INGREDIENT_CATEGORY_HINTS)) {
    for (const name of names) {
      map.set(stemIngredient(name), hint);
    }
  }
  return map;
})();

// Erkennt eine Kategorie-Hinweis-Gruppe für eine Zutat (null wenn unbekannt).
function guessIngredientHint(name) {
  if (!name) return null;
  const stem = stemIngredient(name);
  if (!stem) return null;
  // 1. Direkter Treffer
  if (INGREDIENT_HINT_LOOKUP.has(stem)) {
    return INGREDIENT_HINT_LOOKUP.get(stem);
  }
  // 2. Enthält einen der bekannten Stämme (z.B. "kirsch-tomate")
  for (const [knownStem, hint] of INGREDIENT_HINT_LOOKUP.entries()) {
    if (knownStem.length >= 4 && stem.includes(knownStem)) return hint;
    if (stem.length >= 4 && knownStem.includes(stem)) return hint;
  }
  // 3. Fuzzy-Fallback auf knowledge base (1 Edit-Fehler)
  for (const [knownStem, hint] of INGREDIENT_HINT_LOOKUP.entries()) {
    if (Math.abs(knownStem.length - stem.length) > 2) continue;
    if (knownStem.length < 4) continue;
    if (editDistance(knownStem, stem) <= 1) return hint;
  }
  return null;
}

// Mapping Hint → typische Keywords die in Kategorie-Namen des Users
// enthalten sein könnten. Wir matchen permissiv, damit verschiedene
// Benennungen greifen.
const HINT_TO_CATEGORY_KEYWORDS = {
  obst:       ["obst","früchte","fruechte"],
  gemuese:    ["gemüse","gemuese","salat","kräuter","kraeuter"],
  milch:      ["milch","molkerei","käse","kaese","eier","joghurt"],
  fleisch:    ["fleisch","wurst","metzgerei"],
  fisch:      ["fisch","meer"],
  backwaren:  ["brot","backwar","bäckerei","baeckerei","gebäck","gebaeck"],
  getreide:   ["getreide","nudel","pasta","reis","teigwar","haferflocken"],
  konserven:  ["konserv","dose","glas","fertig"],
  oele:       ["öl","oel","essig","fett"],
  gewuerze:   ["gewürz","gewuerz","zucker","back","mehl"],
  getraenke:  ["getränk","getraenk","drink","saft","wasser"],
  tiefkuehl:  ["tiefkühl","tiefkuehl","tk","gefrier"],
  suess:      ["süß","suess","snack","knabber","nuss","nuesse","schoko","naschen"],
};

// Versucht, für eine Zutat die best passende Benutzer-Kategorie zu finden.
// Gibt null zurück, wenn keine Zuordnung möglich ist.
function autoGuessCategoryId(ingredientName, userCategories) {
  const hint = guessIngredientHint(ingredientName);
  if (!hint) return null;
  const keywords = HINT_TO_CATEGORY_KEYWORDS[hint] || [hint];
  for (const cat of userCategories || []) {
    const catName = (cat.name || "").toLowerCase();
    if (!catName) continue;
    if (keywords.some((kw) => catName.includes(kw))) return cat.id;
  }
  return null;
}

function matchRecipe(recipe, availableNames, staplesEnabled) {
  const recipeIngredients = (recipe.ingredients || []).filter((i) => i.name?.trim());
  if (recipeIngredients.length === 0) {
    return { score: 0, total: 0, have: 0, missing: [], covered: [] };
  }

  const stapleSet = staplesEnabled ? new Set(STAPLE_INGREDIENTS) : new Set();
  const availNormalized = availableNames
    .map((n) => normalizeIngredientName(n))
    .filter(Boolean);

  const missing = [];
  const covered = [];

  for (const ing of recipeIngredients) {
    const norm = normalizeIngredientName(ing.name);
    const isStaple = [...stapleSet].some((s) => ingredientMatches(norm, s));
    if (isStaple) {
      covered.push({ ...ing, isStaple: true });
      continue;
    }
    const found = availNormalized.some((a) => ingredientMatches(a, norm));
    if (found) covered.push({ ...ing });
    else missing.push(ing);
  }

  const relevantTotal = recipeIngredients.length;
  const have = covered.length;
  const score = relevantTotal > 0 ? have / relevantTotal : 0;

  return {
    score,
    total: relevantTotal,
    have,
    missing,
    covered,
  };
}

// Sammelt alle einzigartigen Zutaten aus allen Rezepten, sortiert nach
// Häufigkeit (desc), dann alphabetisch. Case-insensitive dedupliziert,
// aber die erste gesehene Schreibweise wird als Anzeige-Name behalten.
// Grundzutaten (Salz, Pfeffer etc.) werden ans Ende sortiert und mit
// isStaple markiert, damit die Pills-Komponente sie dezenter darstellen kann.
function getIngredientSuggestions(recipes) {
  const map = new Map();
  for (const r of recipes || []) {
    for (const ing of r.ingredients || []) {
      const rawName = (ing.name || "").trim();
      if (!rawName) continue;
      const cleanName = displayIngredientName(rawName);
      if (!cleanName) continue;
      const key = canonicalBaseKey(rawName);
      if (!key) continue;
      const existing = map.get(key);
      if (existing) {
        existing.count++;
        existing.variants.add(cleanName);
        // Kürzeste, "Basis"-nächste Schreibweise als Display wählen
        // (kein Komma, keine Klammern → ist die "blanke" Form).
        const isBaseForm = !/[,(]/.test(cleanName);
        const currentIsBase = !/[,(]/.test(existing.name);
        if (isBaseForm && (!currentIsBase || cleanName.length < existing.name.length)) {
          existing.name = cleanName;
        } else if (!currentIsBase && cleanName.length < existing.name.length) {
          existing.name = cleanName;
        }
      } else {
        const isStaple = STAPLE_INGREDIENTS.some((s) => ingredientMatches(key, s));
        map.set(key, {
          name: cleanName,
          count: 1,
          key,
          isStaple,
          variants: new Set([cleanName]),
        });
      }
    }
  }
  const arr = Array.from(map.values()).map((v) => ({
    ...v,
    variants: [...v.variants].sort((a, b) => a.localeCompare(b, "de")),
  }));
  arr.sort((a, b) => {
    if (a.isStaple !== b.isStaple) return a.isStaple ? 1 : -1;
    if (b.count !== a.count) return b.count - a.count;
    return a.name.localeCompare(b.name, "de");
  });
  return arr;
}

// ────────────────────────────────────────────────────────────────────
// Seed Recipes
// ────────────────────────────────────────────────────────────────────
const SEED_RECIPES = [
  {
    id: uid(),
    name: "Spaghetti Bolognese",
    basePortions: 4,
    notes: "Klassiker – lange schmoren lassen für mehr Geschmack.",
    ingredients: [
      { name: "Spaghetti", amount: 500, unit: "g" },
      { name: "Hackfleisch", amount: 500, unit: "g" },
      { name: "Zwiebel", amount: 2, unit: "Stück" },
      { name: "Knoblauch", amount: 2, unit: "Zehe" },
      { name: "Tomaten (passiert)", amount: 700, unit: "g" },
      { name: "Olivenöl", amount: 2, unit: "EL" },
      { name: "Parmesan", amount: 80, unit: "g" },
    ],
  },
  {
    id: uid(),
    name: "Ofengemüse mit Feta",
    basePortions: 2,
    notes: "Blech rein und vergessen. 200°C, ca. 35 Min.",
    ingredients: [
      { name: "Zucchini", amount: 1, unit: "Stück" },
      { name: "Paprika", amount: 2, unit: "Stück" },
      { name: "Cherrytomaten", amount: 250, unit: "g" },
      { name: "Feta", amount: 200, unit: "g" },
      { name: "Olivenöl", amount: 3, unit: "EL" },
      { name: "Kräuter der Provence", amount: 1, unit: "TL" },
    ],
  },
];

// ════════════════════════════════════════════════════════════════════
// HILFE-SCHICHT 1: EmptyState — statisch, ersetzt leere Listen
// ════════════════════════════════════════════════════════════════════
function EmptyState({ icon, title, hint, primaryAction, secondaryAction }) {
  return (
    <div style={{ textAlign: "center", padding: "2.5rem 1.25rem", maxWidth: "22rem", margin: "0 auto" }}>
      <div
        style={{
          width: "3.25rem",
          height: "3.25rem",
          borderRadius: "50%",
          background: "rgba(61, 74, 42, 0.1)",
          color: "#3d4a2a",
          display: "inline-flex",
          alignItems: "center",
          justifyContent: "center",
          marginBottom: "0.85rem",
        }}
      >
        {icon}
      </div>
      <div className="font-display" style={{ fontWeight: 600, fontSize: "1.05rem", color: "#2a2620", marginBottom: "0.4rem" }}>
        {title}
      </div>
      <div style={{ fontSize: "0.88rem", color: "#6b6358", lineHeight: 1.55, marginBottom: primaryAction || secondaryAction ? "1.1rem" : 0 }}>
        {hint}
      </div>
      {(primaryAction || secondaryAction) && (
        <div style={{ display: "flex", gap: "0.5rem", justifyContent: "center", flexWrap: "wrap" }}>
          {primaryAction && (
            <button className="btn-primary" onClick={primaryAction.onClick}>
              {primaryAction.label}
            </button>
          )}
          {secondaryAction && (
            <button className="btn-ghost" onClick={secondaryAction.onClick}>
              {secondaryAction.label}
            </button>
          )}
        </div>
      )}
    </div>
  );
}

// ════════════════════════════════════════════════════════════════════
// HILFE-SCHICHT 2: HelpTooltip — Hover-Erklärung am ?-Icon
// Verwendung: <HelpTooltip text="..." />
// ════════════════════════════════════════════════════════════════════
function HelpTooltip({ text, placement = "auto", size = 14 }) {
  const [open, setOpen] = useState(false);
  const [pos, setPos] = useState({ side: "top", align: "center" });
  const triggerRef = useRef(null);
  const tooltipRef = useRef(null);

  useLayoutEffect(() => {
    if (!open || !triggerRef.current || !tooltipRef.current) return;
    const trig = triggerRef.current.getBoundingClientRect();
    const tip = tooltipRef.current.getBoundingClientRect();
    const vw = window.innerWidth;
    const margin = 8;
    let side = placement === "auto" ? "top" : placement;
    if (placement === "auto" && trig.top - tip.height - margin < 0) side = "bottom";
    let align = "center";
    const center = trig.left + trig.width / 2;
    if (center - tip.width / 2 < margin) align = "start";
    else if (center + tip.width / 2 > vw - margin) align = "end";
    setPos({ side, align });
  }, [open, placement]);

  const tooltipStyle = (() => {
    const base = {
      position: "absolute",
      background: "#2a2620",
      color: "#fdfaf3",
      fontSize: "0.78rem",
      lineHeight: 1.45,
      padding: "0.5rem 0.7rem",
      borderRadius: "6px",
      maxWidth: "16rem",
      width: "max-content",
      boxShadow: "0 8px 24px rgba(0,0,0,0.18)",
      zIndex: 150,
      pointerEvents: "none",
    };
    if (pos.side === "top") base.bottom = "calc(100% + 6px)";
    else base.top = "calc(100% + 6px)";
    if (pos.align === "center") {
      base.left = "50%";
      base.transform = "translateX(-50%)";
    } else if (pos.align === "start") {
      base.left = 0;
    } else {
      base.right = 0;
    }
    return base;
  })();

  return (
    <span
      ref={triggerRef}
      style={{ display: "inline-flex", position: "relative", verticalAlign: "middle", marginLeft: "0.25rem" }}
      onMouseEnter={() => setOpen(true)}
      onMouseLeave={() => setOpen(false)}
      onFocus={() => setOpen(true)}
      onBlur={() => setOpen(false)}
      onClick={(e) => { e.stopPropagation(); setOpen((v) => !v); }}
    >
      <span
        tabIndex={0}
        role="button"
        aria-label="Hilfe anzeigen"
        style={{
          width: `${size}px`,
          height: `${size}px`,
          borderRadius: "50%",
          background: "rgba(61, 74, 42, 0.12)",
          color: "#3d4a2a",
          display: "inline-flex",
          alignItems: "center",
          justifyContent: "center",
          fontSize: `${size - 5}px`,
          fontWeight: 600,
          cursor: "help",
          fontFamily: "inherit",
          userSelect: "none",
          outline: "none",
        }}
      >
        ?
      </span>
      {open && <span ref={tooltipRef} style={tooltipStyle} role="tooltip">{text}</span>}
    </span>
  );
}

// ════════════════════════════════════════════════════════════════════
// HILFE-SCHICHT 3: GuidedTour — automatisch beim ersten Login
// Targets via data-tour="<id>" auf DOM-Elementen.
// Persistenz: localStorage-Key pro User → Tour läuft genau 1×.
// ════════════════════════════════════════════════════════════════════
const TOUR_KEY = (userId) => `loeffelliste_tour_done_${userId}`;

function resetTour(userId) {
  if (userId) localStorage.removeItem(TOUR_KEY(userId));
}

const LOEFFELLISTE_TOUR_STEPS = [
  {
    target: null,
    title: "Willkommen bei Löffelliste 🥄",
    body: "Eine kurze Tour (≈ 30 Sek), damit du sofort loslegen kannst. Du kannst sie jederzeit überspringen.",
  },
  {
    target: "tab-plan",
    title: "Wochenplan",
    body: "Hier weist du jedem Tag ein Rezept zu. Tipp auf einen Tag → Rezept wählen → Portionen einstellen.",
  },
  {
    target: "tab-recipes",
    title: "Rezepte",
    body: "Deine eigenen Rezepte plus „Entdecken\" für öffentliche Rezepte anderer Nutzer:innen.",
  },
  {
    target: "tab-shopping",
    title: "Einkaufsliste",
    body: "Wird automatisch aus deinem Wochenplan zusammengestellt — gleiche Zutaten werden addiert.",
  },
  {
    target: "tab-finance",
    title: "Haushaltskasse",
    body: "Trackt Einkäufe der WG/Familie und schlägt den optimalen Geld-Ausgleich vor.",
  },
  {
    target: "help-button",
    title: "Hilfe jederzeit verfügbar",
    body: "Über das ?-Icon erreichst du die volle Anleitung — und kannst diese Tour neu starten.",
  },
];

function GuidedTour({ userId, steps, enabled }) {
  const [active, setActive] = useState(false);
  const [idx, setIdx] = useState(0);
  const [rect, setRect] = useState(null);

  useEffect(() => {
    if (!enabled || !userId) return;
    if (localStorage.getItem(TOUR_KEY(userId)) === "1") return;
    const t = setTimeout(() => setActive(true), 700);
    return () => clearTimeout(t);
  }, [enabled, userId]);

  useLayoutEffect(() => {
    if (!active) return;
    const step = steps[idx];
    if (!step?.target) {
      setRect(null);
      return;
    }
    const el = document.querySelector(`[data-tour="${step.target}"]`);
    if (!el) {
      setRect(null);
      return;
    }
    const update = () => {
      const r = el.getBoundingClientRect();
      setRect({ top: r.top, left: r.left, width: r.width, height: r.height });
    };
    update();
    window.addEventListener("resize", update);
    window.addEventListener("scroll", update, true);
    el.scrollIntoView({ behavior: "smooth", block: "center" });
    return () => {
      window.removeEventListener("resize", update);
      window.removeEventListener("scroll", update, true);
    };
  }, [active, idx, steps]);

  const finish = () => {
    if (userId) localStorage.setItem(TOUR_KEY(userId), "1");
    setActive(false);
    setIdx(0);
  };

  if (!active || !steps?.length) return null;
  const step = steps[idx];
  const last = idx === steps.length - 1;

  const bubblePos = (() => {
    const padding = 12;
    const bubbleH = 200;
    if (!rect) {
      return { top: "50%", left: "50%", transform: "translate(-50%, -50%)", width: "min(22rem, calc(100vw - 2rem))" };
    }
    const spaceBelow = window.innerHeight - (rect.top + rect.height);
    const placeBelow = spaceBelow > bubbleH + padding;
    const top = placeBelow ? rect.top + rect.height + padding : Math.max(padding, rect.top - bubbleH - padding);
    return { top: `${top}px`, left: "50%", transform: "translateX(-50%)", width: "min(22rem, calc(100vw - 2rem))" };
  })();

  return (
    <div role="dialog" aria-label={`Tour-Schritt ${idx + 1} von ${steps.length}`} style={{ position: "fixed", inset: 0, zIndex: 300 }}>
      {rect ? (
        <div
          style={{
            position: "fixed",
            top: rect.top - 6,
            left: rect.left - 6,
            width: rect.width + 12,
            height: rect.height + 12,
            borderRadius: "8px",
            boxShadow: "0 0 0 9999px rgba(0,0,0,0.55)",
            border: "2px solid #c06a43",
            pointerEvents: "none",
            transition: "all 0.25s ease",
          }}
        />
      ) : (
        <div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.55)", pointerEvents: "none" }} />
      )}
      <div
        className="scale-in"
        style={{
          position: "fixed",
          background: "#fdfaf3",
          borderRadius: "12px",
          padding: "1rem 1.15rem",
          boxShadow: "0 16px 40px rgba(0,0,0,0.3)",
          ...bubblePos,
        }}
      >
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "0.4rem" }}>
          <span style={{ fontSize: "0.72rem", color: "#8a8275", letterSpacing: "0.06em", textTransform: "uppercase", fontWeight: 600 }}>
            Schritt {idx + 1} von {steps.length}
          </span>
          <button className="icon-btn" onClick={finish} aria-label="Tour schließen" style={{ width: "1.6rem", height: "1.6rem" }}>
            <X size={14} />
          </button>
        </div>
        <h3 className="font-display" style={{ margin: "0 0 0.4rem", fontSize: "1.05rem", fontWeight: 600, color: "#2a2620" }}>
          {step.title}
        </h3>
        <p style={{ margin: "0 0 0.9rem", fontSize: "0.88rem", color: "#4a443b", lineHeight: 1.55 }}>
          {step.body}
        </p>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: "0.5rem", flexWrap: "wrap" }}>
          <button
            onClick={finish}
            style={{ background: "transparent", border: "none", color: "#8a8275", fontSize: "0.8rem", cursor: "pointer", fontFamily: "inherit", padding: "0.3rem 0" }}
          >
            Tour überspringen
          </button>
          <div style={{ display: "flex", alignItems: "center", gap: "0.6rem" }}>
            <div style={{ display: "flex", gap: "4px" }}>
              {steps.map((_, i) => (
                <span
                  key={i}
                  style={{
                    width: "5px",
                    height: "5px",
                    borderRadius: "50%",
                    background: i === idx ? "#3d4a2a" : "#d6cdb8",
                    transition: "background 0.2s",
                  }}
                />
              ))}
            </div>
            {idx > 0 && (
              <button
                className="btn-ghost"
                onClick={() => setIdx(idx - 1)}
                style={{ padding: "0.4rem 0.7rem", fontSize: "0.82rem" }}
              >
                <ChevronLeft size={14} /> Zurück
              </button>
            )}
            <button
              className="btn-primary"
              onClick={() => (last ? finish() : setIdx(idx + 1))}
              style={{ padding: "0.5rem 0.95rem", fontSize: "0.85rem" }}
            >
              {last ? "Los geht's" : <>Weiter <ChevronRight size={14} /></>}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Main App
// ────────────────────────────────────────────────────────────────────
function MealPlannerApp() {
  const [tab, setTab] = useState("plan");
  const [recipes, setRecipes] = useState([]);
  const [plans, setPlans] = useState({});
  const [categories, setCategories] = useState([]);
  const [ingredientCategories, setIngredientCategories] = useState({});
  const [weekMonday, setWeekMonday] = useState(() => getMonday());
  const [loading, setLoading] = useState(true);
  const [toast, setToast] = useState(null);
  const [editingRecipe, setEditingRecipe] = useState(null);
  const [viewHistoryWeek, setViewHistoryWeek] = useState(null);
  const [lastSync, setLastSync] = useState(null);
  const [showShareInfo, setShowShareInfo] = useState(false);
  const [syncing, setSyncing] = useState(false);
  const [userName, setUserName] = useState("");
  const [userEmail, setUserEmail] = useState("");
  const [session, setSession] = useState(null);
  const [showNameModal, setShowNameModal] = useState(false);
  const [confirmState, setConfirmState] = useState(null);
  const [showProfileMenu, setShowProfileMenu] = useState(false);
  const [showHelp, setShowHelp] = useState(false);
  const [showMoreSheet, setShowMoreSheet] = useState(false);
  const [viewingRecipeId, setViewingRecipeId] = useState(null);

  // ── Workspace-Zustand ──
  const [workspace, setWorkspace] = useState(null);        // { id, name, inviteCode, ownerId, role } | null
  const [workspaceMembers, setWorkspaceMembers] = useState([]);
  const [needsOnboarding, setNeedsOnboarding] = useState(false);
  const [publicRecipes, setPublicRecipes] = useState([]);
  const [publicRecipesLoadedAt, setPublicRecipesLoadedAt] = useState(null);
  const [recipeMode, setRecipeMode] = useState("own");     // 'own' | 'discover'
  const [viewingPublicRecipeId, setViewingPublicRecipeId] = useState(null);
  const [planningPublicRecipe, setPlanningPublicRecipe] = useState(null); // { recipe } | null

  // ── Haushaltskasse ──
  const [purchases, setPurchases] = useState([]);
  const [settlements, setSettlements] = useState([]);
  const [editingPurchase, setEditingPurchase] = useState(null); // null | {} (neu) | purchaseRow (edit)
  const [viewingPurchase, setViewingPurchase] = useState(null); // read-only Detail-View für abgerechnete Käufe

  // ── Einkaufslisten-Häkchen (synchronisiert über Workspace) ──
  // shoppingChecks: aktuell abgehakt (im Wagen)
  // purchasedItems: bereits gekauft → werden aus der Liste ausgeblendet
  const [shoppingChecks, setShoppingChecks] = useState(() => new Set());
  const [purchasedItems, setPurchasedItems] = useState(() => new Set());

  const loadFromStorage = async (isInitial, wsOverride) => {
    if (!isInitial) setSyncing(true);
    try {
      const ws = wsOverride || workspace;
      if (!ws) {
        if (!isInitial) setTimeout(() => setSyncing(false), 400);
        return;
      }
      const [r, p, c, ic, members, pu, st, sc] = await Promise.all([
        db.listRecipes(ws.id),
        db.listPlans(ws.id),
        db.listCategories(ws.id),
        db.listIngredientCategories(ws.id),
        db.listMembers().catch(() => []),
        db.listPurchases(ws.id).catch(() => []),
        db.listSettlements(ws.id).catch(() => []),
        db.listShoppingChecks(ws.id).catch(() => ({ checked: new Set(), purchased: new Set() })),
      ]);
      setRecipes(r);
      setPlans(p);
      setCategories(c);
      setIngredientCategories(ic);
      setWorkspaceMembers(members);
      setPurchases(pu);
      setSettlements(st);
      setShoppingChecks(sc.checked || new Set());
      setPurchasedItems(sc.purchased || new Set());
      setLastSync(new Date());
    } catch (e) {
      console.error("Laden fehlgeschlagen:", e);
      showToast("Verbindung zu Supabase fehlgeschlagen – siehe Konsole", "err");
    }
    if (!isInitial) setTimeout(() => setSyncing(false), 400);
  };

  // Workspace-Info laden & ggf. Onboarding triggern
  const refreshWorkspace = async () => {
    try {
      const ws = await db.getMyWorkspace();
      if (ws) {
        setWorkspace(ws);
        setNeedsOnboarding(false);
        await loadFromStorage(true, ws);
      } else {
        setWorkspace(null);
        setNeedsOnboarding(true);
      }
      return ws;
    } catch (e) {
      console.error("Workspace-Abruf fehlgeschlagen:", e);
      setNeedsOnboarding(true);
      return null;
    }
  };

  useEffect(() => {
    (async () => {
      const { data } = await supabase.auth.getSession();
      if (data.session) {
        setSession(data.session);
        const user = data.session.user;
        setUserEmail(user.email || "");
        const displayName = user.user_metadata?.display_name || "";
        setUserName(displayName);
        await refreshWorkspace();
      }
      setLoading(false);
    })();

    const { data: sub } = supabase.auth.onAuthStateChange((_event, newSession) => {
      setSession(newSession);
      if (newSession?.user) {
        setUserEmail(newSession.user.email || "");
        setUserName(newSession.user.user_metadata?.display_name || "");
        refreshWorkspace();
      } else {
        setRecipes([]);
        setPlans({});
        setCategories([]);
        setIngredientCategories({});
        setWorkspace(null);
        setWorkspaceMembers([]);
        setNeedsOnboarding(false);
        setUserEmail("");
        setUserName("");
      }
    });

    return () => sub.subscription.unsubscribe();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const handleLogin = async (email, password) => {
    const { error } = await supabase.auth.signInWithPassword({ email, password });
    if (error) throw error;
  };
  const handleRegister = async (email, password, displayName) => {
    const { error } = await supabase.auth.signUp({
      email,
      password,
      options: { data: { display_name: displayName } },
    });
    if (error) throw error;
  };
  const handleLogout = async () => {
    const ok = await askConfirm({
      title: "Abmelden?",
      message: "Du wirst zum Login-Bildschirm zurückgeführt. Deine Daten bleiben natürlich erhalten.",
      confirmLabel: "Abmelden",
    });
    if (!ok) return;
    await supabase.auth.signOut();
    setShowProfileMenu(false);
    setTab("plan");
  };

  const saveUserName = async (name) => {
    const clean = (name || "").trim().slice(0, 24);
    if (!clean) return;
    setUserName(clean);
    try {
      await supabase.auth.updateUser({ data: { display_name: clean } });
    } catch (e) {
      console.error("Name-Update fehlgeschlagen:", e);
    }
    setShowNameModal(false);
    showToast(`Name aktualisiert: ${clean}`);
  };

  useEffect(() => {
    if (!session || !workspace?.id) return;
    const wsId = workspace.id;
    const channel = supabase
      .channel(`wochenkueche-${wsId}`)
      .on(
        "postgres_changes",
        { event: "*", schema: "public", table: "recipes", filter: `workspace_id=eq.${wsId}` },
        () => loadFromStorage(false)
      )
      .on(
        "postgres_changes",
        { event: "*", schema: "public", table: "plans", filter: `workspace_id=eq.${wsId}` },
        () => loadFromStorage(false)
      )
      .on(
        "postgres_changes",
        { event: "*", schema: "public", table: "categories", filter: `workspace_id=eq.${wsId}` },
        () => loadFromStorage(false)
      )
      .on(
        "postgres_changes",
        { event: "*", schema: "public", table: "ingredient_categories", filter: `workspace_id=eq.${wsId}` },
        () => loadFromStorage(false)
      )
      .on(
        "postgres_changes",
        { event: "*", schema: "public", table: "workspace_members", filter: `workspace_id=eq.${wsId}` },
        async () => {
          try {
            const members = await db.listMembers();
            setWorkspaceMembers(members);
          } catch { /* ignore */ }
        }
      )
      .on(
        "postgres_changes",
        { event: "*", schema: "public", table: "purchases", filter: `workspace_id=eq.${wsId}` },
        async () => {
          try {
            const pu = await db.listPurchases(wsId);
            setPurchases(pu);
          } catch { /* ignore */ }
        }
      )
      .on(
        "postgres_changes",
        { event: "*", schema: "public", table: "settlements", filter: `workspace_id=eq.${wsId}` },
        async () => {
          try {
            const st = await db.listSettlements(wsId);
            setSettlements(st);
          } catch { /* ignore */ }
        }
      )
      .on(
        "postgres_changes",
        { event: "*", schema: "public", table: "shopping_checks", filter: `workspace_id=eq.${wsId}` },
        async () => {
          try {
            const sc = await db.listShoppingChecks(wsId);
            setShoppingChecks(sc.checked || new Set());
            setPurchasedItems(sc.purchased || new Set());
          } catch { /* ignore */ }
        }
      )
      .subscribe();
    return () => {
      supabase.removeChannel(channel);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [session, workspace?.id]);

  useEffect(() => {
    if (!session || !workspace?.id) return;
    const onFocus = () => loadFromStorage(false);
    window.addEventListener("focus", onFocus);
    return () => window.removeEventListener("focus", onFocus);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [session, workspace?.id]);

  const showToast = (msg, tone = "ok") => {
    setToast({ msg, tone });
    setTimeout(() => setToast(null), 2600);
  };

  const askConfirm = ({ title, message, confirmLabel = "Bestätigen", danger = false }) => {
    return new Promise((resolve) => {
      setConfirmState({
        title,
        message,
        confirmLabel,
        danger,
        onResolve: (result) => {
          setConfirmState(null);
          resolve(result);
        },
      });
    });
  };

  const loadSeedRecipes = async () => {
    if (!workspace) return;
    try {
      const withNewIds = SEED_RECIPES.map((r) => ({ ...r, id: uid() }));
      for (const r of withNewIds) {
        await db.saveRecipe(r, workspace.id);
      }
      setRecipes([...recipes, ...withNewIds]);
      showToast("Beispielrezepte hinzugefügt");
    } catch (e) {
      console.error(e);
      showToast("Hinzufügen fehlgeschlagen – siehe Konsole", "err");
    }
  };

  const saveRecipe = async (recipe) => {
    if (!workspace) return;
    const existing = recipes.find((r) => r.id === recipe.id);
    const clean = {
      ...recipe,
      name: recipe.name.trim(),
      basePortions: Math.max(1, Number(recipe.basePortions) || 1),
      ingredients: recipe.ingredients
        .map((i) => ({
          name: i.name.trim(),
          amount: i.amount === "" ? 0 : Number(i.amount) || 0,
          unit: i.unit || "",
        }))
        .filter((i) => i.name),
      notes: (recipe.notes || "").trim(),
      preparation: (recipe.preparation || "").trim(),
      sourceUrl: (recipe.sourceUrl || "").trim(),
      sourceAuthor: (recipe.sourceAuthor || "").trim(),
      createdBy: existing?.createdBy || userName || "",
      updatedBy: userName || "",
      updatedAt: new Date().toISOString(),
      isPublic: isFromChefkoch(recipe) ? false : !!recipe.isPublic,
      workspaceId: workspace.id,
    };
    if (!clean.name) {
      showToast("Bitte einen Rezeptnamen eingeben", "err");
      return;
    }
    try {
      await db.saveRecipe(clean, workspace.id);
      let next;
      if (recipes.some((r) => r.id === clean.id)) {
        next = recipes.map((r) => (r.id === clean.id ? clean : r));
      } else {
        next = [...recipes, clean];
      }
      setRecipes(next);
      setEditingRecipe(null);
      showToast("Rezept gespeichert");
    } catch (e) {
      console.error(e);
      showToast("Speichern fehlgeschlagen – siehe Konsole", "err");
    }
  };

  // Öffnet den RecipeEditor mit Chefkoch-Daten vorausgefüllt.
  // Der User kann vor dem Speichern noch bearbeiten.
  const importFromChefkoch = (ckDetail) => {
    const mapped = mapChefkochToRecipe(ckDetail);
    setEditingRecipe({
      id: uid(),
      ...mapped,
      ingredients:
        mapped.ingredients.length > 0
          ? mapped.ingredients
          : [{ name: "", amount: "", unit: "g" }],
    });
    showToast("Rezept geladen – prüfen und speichern");
  };

  const deleteRecipe = async (id) => {
    if (!workspace) return;
    const recipe = recipes.find((r) => r.id === id);
    const affectedWeeks = Object.entries(plans).filter(([, p]) =>
      Object.values(p.days || {}).some((d) => d?.recipeId === id)
    );
    const extra =
      affectedWeeks.length > 0
        ? ` Zusätzlich werden ${affectedWeeks.length} geplante Tag${
            affectedWeeks.length === 1 ? "" : "e"
          } geleert, die dieses Rezept nutzten.`
        : "";
    const ok = await askConfirm({
      title: "Rezept löschen?",
      message: `"${recipe?.name || "Dieses Rezept"}" wird aus dem Bestand entfernt.${extra}`,
      confirmLabel: "Löschen",
      danger: true,
    });
    if (!ok) return;
    try {
      await db.cleanUpAfterRecipeDelete(id, workspace.id);
      await db.deleteRecipe(id);
      setRecipes(recipes.filter((r) => r.id !== id));
      const nextPlans = Object.fromEntries(
        Object.entries(plans).map(([week, p]) => [
          week,
          {
            ...p,
            days: Object.fromEntries(
              Object.entries(p.days || {}).filter(([, d]) => d?.recipeId !== id)
            ),
          },
        ])
      );
      setPlans(nextPlans);
      showToast("Rezept gelöscht");
    } catch (e) {
      console.error(e);
      showToast("Löschen fehlgeschlagen – siehe Konsole", "err");
    }
  };

  const currentPlanKey = toISODate(weekMonday);
  const currentPlan = plans[currentPlanKey] || {
    weekStart: currentPlanKey,
    days: {},
    confirmedAt: null,
  };

  const updateCurrentDay = async (dayKey, patch) => {
    if (!workspace) return;
    const nextDays = { ...currentPlan.days };
    const prevDay = currentPlan.days[dayKey] || {};
    const prevRecipeId = prevDay.recipeId;
    if (patch === null) {
      delete nextDays[dayKey];
    } else {
      const recipeChanged =
        patch.recipeId !== undefined && patch.recipeId !== prevRecipeId;
      nextDays[dayKey] = {
        ...prevDay,
        ...patch,
        setBy: userName || "",
        setAt: new Date().toISOString(),
        ...(recipeChanged ? { confirmed: false } : {}),
      };
    }
    const nextPlan = { ...currentPlan, days: nextDays };
    const next = { ...plans, [currentPlanKey]: nextPlan };
    setPlans(next);
    try {
      await db.savePlan(currentPlanKey, nextPlan, workspace.id);
    } catch (e) {
      console.error(e);
      showToast("Speichern fehlgeschlagen – siehe Konsole", "err");
    }
  };

  // ── Tag verschieben (Drag & Drop) ──────────────────────────────────
  // Verschiebt den Inhalt eines Tages auf einen anderen Tag derselben Woche.
  // Falls der Ziel-Tag bereits belegt ist → Tausch (Swap), sonst reine Bewegung.
  // Beide Tage werden in EINEM DB-Write aktualisiert, damit die Operation atomar ist.
  const moveDayPlan = async (srcKey, destKey) => {
    if (!workspace) return;
    if (!srcKey || !destKey || srcKey === destKey) return;
    const srcDay = currentPlan.days?.[srcKey];
    if (!srcDay?.recipeId) return; // Quelle muss belegt sein
    const destDay = currentPlan.days?.[destKey] || null;
    const now = new Date().toISOString();
    const setter = userName || "";

    const nextDays = { ...currentPlan.days };
    if (destDay?.recipeId) {
      // Swap: Inhalte tauschen, beide gelten als geändert
      nextDays[destKey] = { ...srcDay, setBy: setter, setAt: now, confirmed: false };
      nextDays[srcKey] = { ...destDay, setBy: setter, setAt: now, confirmed: false };
    } else {
      // Move: Quelle leeren, Ziel mit Quell-Inhalt füllen
      nextDays[destKey] = { ...srcDay, setBy: setter, setAt: now, confirmed: false };
      delete nextDays[srcKey];
    }
    const nextPlan = { ...currentPlan, days: nextDays };
    const next = { ...plans, [currentPlanKey]: nextPlan };
    setPlans(next);
    try {
      await db.savePlan(currentPlanKey, nextPlan, workspace.id);
      showToast(destDay?.recipeId ? "Tage getauscht" : "Verschoben");
    } catch (e) {
      console.error(e);
      showToast("Verschieben fehlgeschlagen", "err");
      // Optimistic-UI-Rollback
      setPlans(plans);
    }
  };

  const toggleFavorite = async (recipe) => {
    const nextVal = !recipe.isFavorite;
    setRecipes(
      recipes.map((r) => (r.id === recipe.id ? { ...r, isFavorite: nextVal } : r))
    );
    try {
      await db.toggleFavorite(recipe.id, nextVal);
      showToast(nextVal ? "Als Favorit markiert ⭐" : "Favorit entfernt");
    } catch (e) {
      console.error(e);
      setRecipes(
        recipes.map((r) => (r.id === recipe.id ? { ...r, isFavorite: recipe.isFavorite } : r))
      );
      showToast("Favorit ändern fehlgeschlagen", "err");
    }
  };

  const togglePublic = async (recipe) => {
    if (isFromChefkoch(recipe)) {
      showToast(
        "Chefkoch-Rezepte können aus urheberrechtlichen Gründen nicht veröffentlicht werden",
        "err"
      );
      return;
    }
    const nextVal = !recipe.isPublic;
    setRecipes(
      recipes.map((r) => (r.id === recipe.id ? { ...r, isPublic: nextVal } : r))
    );
    try {
      await db.togglePublic(recipe.id, nextVal);
      showToast(
        nextVal
          ? "Öffentlich geteilt – alle App-Nutzer:innen können es sehen"
          : "Nicht mehr öffentlich – nur dein Arbeitsbereich sieht es"
      );
    } catch (e) {
      console.error(e);
      setRecipes(
        recipes.map((r) => (r.id === recipe.id ? { ...r, isPublic: recipe.isPublic } : r))
      );
      showToast("Teilen/Beenden fehlgeschlagen", "err");
    }
  };

  // ── Entdecken: öffentliche Rezepte nachladen ──
  const loadPublicRecipes = async () => {
    if (!workspace) return;
    try {
      const list = await db.listPublicRecipes(workspace.id);
      setPublicRecipes(list);
      setPublicRecipesLoadedAt(new Date());
    } catch (e) {
      console.error(e);
      showToast("Öffentliche Rezepte konnten nicht geladen werden", "err");
    }
  };

  // ── Öffentliches Rezept in einen Tag einplanen (Snapshot) ──
  const planPublicRecipeInDay = async (publicRecipe, dayKey, portions, targetWeekKey) => {
    if (!workspace) return;
    const weekKey = targetWeekKey || currentPlanKey;
    const plan = plans[weekKey] || { weekStart: weekKey, days: {}, confirmedAt: null };
    const prevDay = plan.days[dayKey] || {};
    const snapshot = {
      name: publicRecipe.name,
      basePortions: publicRecipe.basePortions,
      ingredients: publicRecipe.ingredients,
      recipeCategory: publicRecipe.recipeCategory,
      notes: publicRecipe.notes || "",
      sourceWorkspaceId: publicRecipe.workspaceId,
      sourceAuthor: publicRecipe.createdBy || "",
    };
    const newDay = {
      ...prevDay,
      recipeId: publicRecipe.id,
      portions: Math.max(1, Math.min(99, Number(portions) || publicRecipe.basePortions || 2)),
      recipeSnapshot: snapshot,
      setBy: userName || "",
      setAt: new Date().toISOString(),
      confirmed: false,
    };
    const nextPlan = {
      ...plan,
      days: { ...plan.days, [dayKey]: newDay },
    };
    setPlans({ ...plans, [weekKey]: nextPlan });
    try {
      await db.savePlan(weekKey, nextPlan, workspace.id);
      showToast(`"${publicRecipe.name}" eingeplant`);
    } catch (e) {
      console.error(e);
      showToast("Einplanen fehlgeschlagen – siehe Konsole", "err");
    }
  };

  // ── Workspace-Aktionen (Onboarding + Einstellungen) ──
  const handleCreateWorkspace = async (name) => {
    const result = await db.createWorkspace(name);
    if (!result?.success) {
      throw new Error(result?.error || "Anlegen fehlgeschlagen");
    }
    await refreshWorkspace();
    showToast("Arbeitsbereich angelegt");
  };

  const handleJoinWorkspace = async (code) => {
    const result = await db.joinWorkspace(code);
    if (!result?.success) {
      if (result?.error === "invalid_code") {
        throw new Error("Der Einladungs-Code ist ungültig oder abgelaufen.");
      }
      if (result?.error === "already_in_other_workspace") {
        throw new Error("Du bist bereits in einem anderen Arbeitsbereich. Verlasse diesen zuerst.");
      }
      throw new Error(result?.error || "Beitritt fehlgeschlagen");
    }
    await refreshWorkspace();
    showToast(`Willkommen in "${result.workspace_name}"`);
  };

  const handleRegenerateInvite = async () => {
    const result = await db.regenerateInvite();
    if (!result?.success) {
      showToast("Code konnte nicht neu erzeugt werden", "err");
      return;
    }
    setWorkspace((prev) => (prev ? { ...prev, inviteCode: result.invite_code } : prev));
    showToast("Neuer Einladungs-Code generiert");
  };

  // Einmalige Bereinigung: entfernt "[Sektion:]"-Präfixe aus Zutatennamen
  // in allen Rezepten und überträgt die Sektion (falls noch nicht vorhanden)
  // in das separate `section`-Feld der Zutat.
  const handleCleanupIngredientNames = async () => {
    if (!workspace) return;
    let recipesTouched = 0;
    let ingredientsTouched = 0;
    const updated = [];
    for (const r of recipes) {
      let changed = false;
      const nextIngs = (r.ingredients || []).map((ing) => {
        const orig = (ing?.name || "").trim();
        if (!orig) return ing;
        const m = orig.match(/^\s*\[([^\]]+)\]\s*(.*)$/);
        if (!m) return ing;
        const sectionRaw = m[1].trim().replace(/:$/, "").trim();
        const cleaned = m[2].trim();
        if (!cleaned) return ing;
        changed = true;
        ingredientsTouched++;
        return {
          ...ing,
          name: cleaned,
          ...(sectionRaw && !ing.section ? { section: sectionRaw } : {}),
        };
      });
      if (changed) {
        recipesTouched++;
        updated.push({ ...r, ingredients: nextIngs });
      }
    }
    if (recipesTouched === 0) {
      showToast("Nichts zu bereinigen – Zutatennamen sind sauber");
      return;
    }
    try {
      for (const r of updated) {
        await db.saveRecipe(r, workspace.id);
      }
      const byId = new Map(updated.map((r) => [r.id, r]));
      setRecipes((prev) => prev.map((r) => byId.get(r.id) || r));
      showToast(
        `${ingredientsTouched} Zutat${ingredientsTouched === 1 ? "" : "en"} in ${recipesTouched} Rezept${
          recipesTouched === 1 ? "" : "en"
        } bereinigt`
      );
    } catch (e) {
      console.error(e);
      showToast("Bereinigung fehlgeschlagen – siehe Konsole", "err");
    }
  };

  const handleLeaveWorkspace = async () => {
    const memberCount = workspaceMembers.length;
    const isLast = memberCount <= 1;
    const ok = await askConfirm({
      title: isLast ? "Arbeitsbereich löschen?" : "Arbeitsbereich verlassen?",
      message: isLast
        ? "Du bist das einzige Mitglied. Der Arbeitsbereich samt aller Rezepte, Wochenpläne und Kategorien wird gelöscht."
        : workspace?.role === "owner"
        ? "Die Besitzrolle geht an das nächstälteste Mitglied über. Du verlierst den Zugriff auf Rezepte und Wochenpläne dieses Arbeitsbereichs."
        : "Du verlierst den Zugriff auf Rezepte und Wochenpläne dieses Arbeitsbereichs.",
      confirmLabel: isLast ? "Löschen" : "Verlassen",
      danger: true,
    });
    if (!ok) return;
    try {
      await db.leaveWorkspace();
      setShowShareInfo(false);
      setRecipes([]);
      setPlans({});
      setCategories([]);
      setIngredientCategories({});
      setWorkspace(null);
      setWorkspaceMembers([]);
      setNeedsOnboarding(true);
      showToast(isLast ? "Arbeitsbereich gelöscht" : "Arbeitsbereich verlassen");
    } catch (e) {
      console.error(e);
      showToast("Verlassen fehlgeschlagen", "err");
    }
  };

  const assignIngredientCategory = async (ingredientName, categoryId) => {
    if (!workspace) return;
    const lower = (ingredientName || "").trim().toLowerCase();
    if (!lower) return;
    setIngredientCategories((prev) => ({
      ...prev,
      [lower]: categoryId || null,
    }));
    try {
      await db.setIngredientCategory(ingredientName, categoryId, workspace.id);
    } catch (e) {
      console.error(e);
      showToast("Kategorie-Zuordnung fehlgeschlagen", "err");
    }
  };

  // Toggle Häkchen einzelner Zutaten – optimistic UI + Server-Sync.
  // Bei Fehler: lokalen Zustand zurückrollen und Toast zeigen.
  const toggleShoppingCheck = async (itemKey) => {
    if (!workspace || !itemKey) return;
    const userId = session?.user?.id;
    const wasChecked = shoppingChecks.has(itemKey);

    // Optimistic update
    setShoppingChecks((prev) => {
      const n = new Set(prev);
      if (wasChecked) n.delete(itemKey);
      else n.add(itemKey);
      return n;
    });

    try {
      if (wasChecked) {
        await db.unsetShoppingCheck(workspace.id, itemKey);
      } else {
        await db.setShoppingCheck(workspace.id, itemKey, userId);
      }
    } catch (e) {
      console.error(e);
      // Rollback
      setShoppingChecks((prev) => {
        const n = new Set(prev);
        if (wasChecked) n.add(itemKey);
        else n.delete(itemKey);
        return n;
      });
      showToast("Sync fehlgeschlagen – versuche es erneut", "err");
    }
  };

  // Markiere abgehakte Items als „gekauft" – sie verschwinden aus der Liste,
  // tauchen aber im „Bereits gekauft"-Bereich auf, von wo sie wiederhergestellt
  // werden können.
  const markItemsPurchased = async (itemKeys) => {
    if (!workspace || !Array.isArray(itemKeys) || itemKeys.length === 0) return;
    const userId = session?.user?.id;
    const before = { c: shoppingChecks, p: purchasedItems };

    // Optimistic
    setShoppingChecks((prev) => {
      const n = new Set(prev);
      for (const k of itemKeys) n.delete(k);
      return n;
    });
    setPurchasedItems((prev) => {
      const n = new Set(prev);
      for (const k of itemKeys) n.add(k);
      return n;
    });

    try {
      await db.markPurchased(workspace.id, itemKeys, userId);
    } catch (e) {
      console.error(e);
      setShoppingChecks(before.c);
      setPurchasedItems(before.p);
      showToast("Konnte Einkauf nicht speichern", "err");
    }
  };

  // Item zurück in den Status „offen" – Eintrag wird komplett entfernt
  const restorePurchasedItem = async (itemKey) => {
    if (!workspace || !itemKey) return;
    const before = { c: shoppingChecks, p: purchasedItems };

    setPurchasedItems((prev) => {
      const n = new Set(prev);
      n.delete(itemKey);
      return n;
    });
    setShoppingChecks((prev) => {
      const n = new Set(prev);
      n.delete(itemKey);
      return n;
    });

    try {
      await db.unmarkPurchased(workspace.id, itemKey);
    } catch (e) {
      console.error(e);
      setShoppingChecks(before.c);
      setPurchasedItems(before.p);
      showToast("Wiederherstellen fehlgeschlagen", "err");
    }
  };

  // Alle Workspace-Häkchen löschen (komplettes Reset, beide States).
  const clearShoppingChecks = async (keys = null) => {
    if (!workspace) return;
    const before = { c: shoppingChecks, p: purchasedItems };

    setShoppingChecks((prev) => {
      if (!keys) return new Set();
      const n = new Set(prev);
      for (const k of keys) n.delete(k);
      return n;
    });
    setPurchasedItems((prev) => {
      if (!keys) return new Set();
      const n = new Set(prev);
      for (const k of keys) n.delete(k);
      return n;
    });

    try {
      await db.clearShoppingChecks(workspace.id, keys);
    } catch (e) {
      console.error(e);
      setShoppingChecks(before.c);
      setPurchasedItems(before.p);
      showToast("Liste konnte nicht zurückgesetzt werden", "err");
    }
  };

  const confirmWeek = async () => {
    const plannedDays = Object.entries(currentPlan.days || {}).filter(
      ([, d]) => d?.recipeId
    );
    if (plannedDays.length === 0) {
      showToast("Noch keine Rezepte für diese Woche geplant", "err");
      return;
    }

    // Welche Tage sind noch nicht gezählt? Mehrfach-Bestätigen darf nicht
    // mehrfach zählen.
    const toBump = []; // [recipeId, ...]
    const nextDays = { ...currentPlan.days };
    for (const [dayKey, day] of plannedDays) {
      if (!day.confirmed) {
        toBump.push(day.recipeId);
        nextDays[dayKey] = { ...day, confirmed: true };
      }
    }

    const nextPlan = {
      ...currentPlan,
      days: nextDays,
      confirmedAt: new Date().toISOString(),
      confirmedBy: userName || "",
    };
    const next = { ...plans, [currentPlanKey]: nextPlan };
    setPlans(next);

    // Lokal optimistisch hochzählen, damit UI sofort reagiert
    if (toBump.length > 0) {
      const bumpCountById = new Map();
      for (const id of toBump) {
        bumpCountById.set(id, (bumpCountById.get(id) || 0) + 1);
      }
      setRecipes((prev) =>
        prev.map((r) => {
          const bumps = bumpCountById.get(r.id) || 0;
          if (bumps === 0) return r;
          return { ...r, usageCount: (r.usageCount || 0) + bumps };
        })
      );
    }

    try {
      await db.savePlan(currentPlanKey, nextPlan, workspace.id);
      // DB-Bumps parallel, Fehler ignorieren (Counter ist nicht kritisch)
      for (const rId of toBump) {
        db.bumpRecipeUsage(rId).catch(() => {});
      }
      setTab("shopping");
      showToast("Woche bestätigt – Einkaufsliste bereit");
    } catch (e) {
      console.error(e);
      showToast("Bestätigen fehlgeschlagen – siehe Konsole", "err");
    }
  };

  const unconfirmWeek = async (weekKey) => {
    if (!workspace) return;
    const key = weekKey || currentPlanKey;
    const targetPlan = plans[key];
    if (!targetPlan) return;
    const nextPlan = { ...targetPlan, confirmedAt: null, confirmedBy: null };
    const next = { ...plans, [key]: nextPlan };
    setPlans(next);
    try {
      await db.savePlan(key, nextPlan, workspace.id);
      showToast("Bestätigung aufgehoben");
    } catch (e) {
      console.error(e);
      showToast("Speichern fehlgeschlagen – siehe Konsole", "err");
    }
  };

  const clearWeek = async () => {
    if (!workspace) return;
    const ok = await askConfirm({
      title: "Woche leeren?",
      message: "Alle Einträge dieser Woche werden entfernt. Deine Rezepte bleiben erhalten.",
      confirmLabel: "Leeren",
      danger: true,
    });
    if (!ok) return;
    const next = { ...plans };
    delete next[currentPlanKey];
    setPlans(next);
    try {
      await db.deletePlan(currentPlanKey, workspace.id);
      showToast("Woche geleert");
    } catch (e) {
      console.error(e);
      showToast("Löschen fehlgeschlagen – siehe Konsole", "err");
    }
  };

  const shoppingList = useMemo(
    () => buildShoppingList(currentPlan, recipes, ingredientCategories, categories),
    [currentPlan, recipes, ingredientCategories, categories]
  );

  if (loading) {
    return (
      <div
        style={{
          minHeight: "100vh",
          background: "#f5efe4",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          color: "#6b6358",
        }}
      >
        Lade…
      </div>
    );
  }

  if (!session) {
    return (
      <AuthScreen onLogin={handleLogin} onRegister={handleRegister} />
    );
  }

  if (needsOnboarding) {
    return (
      <OnboardingScreen
        userName={userName}
        onCreate={handleCreateWorkspace}
        onJoin={handleJoinWorkspace}
        onLogout={async () => { await supabase.auth.signOut(); }}
      />
    );
  }

  if (!workspace) {
    return (
      <div
        style={{
          minHeight: "100vh",
          background: "#f5efe4",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          color: "#6b6358",
        }}
      >
        Lade Arbeitsbereich…
      </div>
    );
  }

  return (
    <div className="min-h-screen w-full" style={{ background: "#f5efe4", color: "#2a2620" }}>
      <style>{`
        @import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,500;0,9..144,600;0,9..144,700;1,9..144,400&family=Instrument+Sans:wght@400;500;600&display=swap');
        *, *::before, *::after { box-sizing: border-box; }
        html, body {
          margin: 0;
          width: 100%;
          max-width: 100%;
          overflow-x: hidden;
        }
        body {
          padding-left: env(safe-area-inset-left);
          padding-right: env(safe-area-inset-right);
        }
        #root, main { max-width: 100%; }
        img, svg, video { max-width: 100%; }
        .font-display { font-family: 'Fraunces', Georgia, serif; font-optical-sizing: auto; }
        .font-body { font-family: 'Instrument Sans', system-ui, sans-serif; }
        body, input, button, select, textarea { font-family: 'Instrument Sans', system-ui, sans-serif; }
        input, select, textarea, button {
          font-size: 16px;
          -webkit-appearance: none;
          appearance: none;
        }
        select {
          background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236b6358' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
          background-repeat: no-repeat;
          background-position: right 0.7rem center;
          padding-right: 2rem !important;
        }
        .stripe-pattern {
          background-image: repeating-linear-gradient(
            45deg,
            transparent 0,
            transparent 8px,
            rgba(61, 74, 42, 0.05) 8px,
            rgba(61, 74, 42, 0.05) 9px
          );
        }
        .input-base {
          background: #fdfaf3;
          border: 1.5px solid #e4dccc;
          border-radius: 8px;
          padding: 0.6rem 0.8rem;
          font-size: 16px;
          color: #2a2620;
          outline: none;
          transition: border-color 0.15s, box-shadow 0.15s;
        }
        .input-base:focus {
          border-color: #3d4a2a;
          box-shadow: 0 0 0 3px rgba(61, 74, 42, 0.12);
        }
        .btn-primary {
          background: #3d4a2a;
          color: #f5efe4;
          padding: 0.6rem 1.1rem;
          border-radius: 8px;
          font-weight: 500;
          letter-spacing: 0.01em;
          display: inline-flex;
          align-items: center;
          gap: 0.5rem;
          border: none;
          cursor: pointer;
          transition: all 0.15s;
          font-size: 0.9rem;
        }
        .btn-primary:hover { background: #2d3820; transform: translateY(-1px); }
        .btn-primary:active { transform: translateY(0) scale(0.98); }
        .btn-primary:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }
        .btn-secondary {
          background: transparent;
          color: #3d4a2a;
          padding: 0.55rem 1rem;
          border-radius: 8px;
          border: 1.5px solid #3d4a2a;
          cursor: pointer;
          font-weight: 500;
          display: inline-flex;
          align-items: center;
          gap: 0.5rem;
          transition: all 0.15s;
          font-size: 0.9rem;
        }
        .btn-secondary:hover { background: rgba(61, 74, 42, 0.08); }
        .btn-ghost {
          background: transparent;
          color: #2a2620;
          padding: 0.45rem 0.75rem;
          border-radius: 6px;
          border: none;
          cursor: pointer;
          display: inline-flex;
          align-items: center;
          gap: 0.4rem;
          transition: all 0.15s;
          font-size: 0.9rem;
        }
        .btn-ghost:hover { background: rgba(42, 38, 32, 0.06); }
        .btn-ghost:active { transform: scale(0.97); }
        .btn-danger { color: #a8422a; }
        .btn-danger:hover { background: rgba(168, 66, 42, 0.08); }
        .icon-btn {
          display: inline-flex;
          align-items: center;
          justify-content: center;
          width: 34px;
          height: 34px;
          border-radius: 8px;
          background: transparent;
          border: 1px solid transparent;
          color: #6b6358;
          cursor: pointer;
          transition: all 0.15s;
        }
        .icon-btn:hover { background: rgba(61, 74, 42, 0.08); color: #2a2620; }
        .icon-btn:active { transform: scale(0.92); }
        .card {
          background: #fdfaf3;
          border: 1px solid #e4dccc;
          border-radius: 12px;
          transition: border-color 0.15s, transform 0.15s, box-shadow 0.15s;
        }
        .card-clickable { cursor: pointer; }
        .card-clickable:hover {
          border-color: #c06a43;
          transform: translateY(-2px);
          box-shadow: 0 6px 18px rgba(192, 106, 67, 0.1);
        }
        /* RecipeCard v2 — leerer Stern nur on-hover sichtbar */
        .recipe-card-v2 .star-toggle.is-empty {
          opacity: 0;
          transition: opacity 0.15s;
        }
        .recipe-card-v2:hover .star-toggle.is-empty,
        .recipe-card-v2 .star-toggle.is-empty:focus-visible {
          opacity: 1;
        }
        /* Auf Touch-Geräten Stern immer leicht sichtbar (kein hover) */
        @media (hover: none) {
          .recipe-card-v2 .star-toggle.is-empty { opacity: 0.35; }
        }
        .tab-row {
          display: flex;
          gap: 0.25rem;
          margin-top: 1rem;
          overflow-x: auto;
          scrollbar-width: none;
        }
        .tab-row::-webkit-scrollbar { display: none; }
        .tab-btn {
          position: relative;
          padding: 0.8rem 0.9rem;
          background: none;
          border: none;
          cursor: pointer;
          color: #8a8275;
          font-weight: 500;
          font-size: 0.92rem;
          display: inline-flex;
          align-items: center;
          gap: 0.4rem;
          letter-spacing: 0.01em;
          transition: color 0.15s;
          white-space: nowrap;
          border-bottom: 2px solid transparent;
        }
        .tab-btn:hover { color: #2a2620; }
        .tab-btn.active {
          color: #3d4a2a;
          border-bottom-color: #c06a43;
        }
        .scale-in { animation: scaleIn 0.18s ease-out; }
        @keyframes scaleIn {
          from { opacity: 0; transform: scale(0.97); }
          to { opacity: 1; transform: scale(1); }
        }
        .fade-in { animation: fadeIn 0.25s ease-out; }
        @keyframes fadeIn {
          from { opacity: 0; transform: translateY(4px); }
          to { opacity: 1; transform: translateY(0); }
        }
        @keyframes mp-spin {
          to { transform: rotate(360deg); }
        }
        /* Während eines aktiven Drags: globaler grabbing-Cursor + keine Text-Auswahl */
        body.ll-dragging,
        body.ll-dragging * {
          cursor: grabbing !important;
          user-select: none !important;
          -webkit-user-select: none !important;
        }
        .pill {
          display: inline-flex;
          align-items: center;
          gap: 0.3rem;
          padding: 0.18rem 0.6rem;
          border-radius: 999px;
          font-size: 0.75rem;
          font-weight: 600;
          letter-spacing: 0.01em;
        }
        .max-w-5xl { max-width: 64rem; }
        .mx-auto { margin-left: auto; margin-right: auto; }
        .px-5 { padding-left: 1.25rem; padding-right: 1.25rem; }
        .pt-4 { padding-top: 1rem; }
        .pb-0 { padding-bottom: 0; }
        .py-8 { padding-top: 2rem; padding-bottom: 2rem; }
        input, select, textarea, button { box-sizing: border-box; }
        html { scroll-padding-top: 5rem; scroll-behavior: smooth; }
        /* Touch-Highlight auf iOS dezenter */
        button, [role="button"], a {
          -webkit-tap-highlight-color: rgba(192, 106, 67, 0.12);
        }
        .mode-switch {
          display: inline-flex;
          padding: 0.25rem;
          background: rgba(61, 74, 42, 0.06);
          border-radius: 8px;
          margin-bottom: 1rem;
          gap: 0.1rem;
        }
        .mode-switch button {
          padding: 0.45rem 0.9rem;
          background: transparent;
          border: none;
          border-radius: 6px;
          cursor: pointer;
          font-size: 0.88rem;
          font-weight: 500;
          color: #6b6358;
          display: inline-flex;
          align-items: center;
          justify-content: center;
          gap: 0.35rem;
          white-space: nowrap;
          transition: background 0.15s, color 0.15s;
        }
        .mode-switch button.active {
          background: #fdfaf3;
          font-weight: 600;
          box-shadow: 0 1px 3px rgba(0,0,0,0.06);
        }
        .mode-switch button.active.mode-own      { color: #3d4a2a; }
        .mode-switch button.active.mode-discover { color: #4a6b8a; }
        .mode-switch button.active.mode-chefkoch { color: #a8422a; }
        .mode-label-short { display: none; }
        .mode-label-long  { display: inline; }
        @media (max-width: 600px) {
          .max-w-5xl {
            padding-left: 0.75rem;
            padding-right: 0.75rem;
            width: 100%;
            max-width: 100%;
          }
          .px-5 { padding-left: 0.85rem; padding-right: 0.85rem; }
          .py-8 { padding-top: 1.25rem; padding-bottom: 1.25rem; }
          .input-base { padding: 0.55rem 0.7rem; font-size: 16px; }
          .card {
            padding: 0.85rem 0.95rem;
            max-width: 100%;
            min-width: 0;
          }
          .mode-switch {
            display: flex;
            width: 100%;
          }
          .mode-switch button {
            flex: 1 1 0;
            padding: 0.5rem 0.3rem;
            font-size: 0.82rem;
            min-width: 0;
          }
          .mode-label-long  { display: none; }
          .mode-label-short { display: inline; }
        }

        /* ─── iOS-Auto-Zoom verhindern (alle Viewports) ─────────────
           iOS Safari zoomt automatisch in Inputs mit font-size < 16px.
           Bewusst NICHT in Media-Query, weil iOS Safari auf Hosts ohne
           korrektes viewport-meta nicht in Mobile-Mode wechselt und
           dann max-width-Queries nicht greifen. */
        input[type="text"],
        input[type="number"],
        input[type="email"],
        input[type="password"],
        input[type="search"],
        input[type="tel"],
        input[type="url"],
        input[type="date"],
        input[type="datetime-local"],
        input:not([type]),
        select,
        textarea {
          font-size: 16px;
        }

        /* ─── Mobile Bottom Navigation ─────────────────────────────── */
        .bottom-nav { display: none; }
        @media (max-width: 640px) {
          .tab-row { display: none !important; }
          /* Header braucht mehr Bottom-Padding wenn Top-Tabs versteckt sind */
          header .pb-0 { padding-bottom: 0.85rem; }
          .bottom-nav {
            display: flex;
            position: fixed;
            bottom: 0;
            left: 0;
            right: 0;
            background: #f8f3e8;
            border-top: 1px solid #e4dccc;
            z-index: 50;
            padding: 0.35rem 0 max(0.35rem, env(safe-area-inset-bottom));
            backdrop-filter: blur(8px);
            box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.04);
          }
          .bottom-nav-btn {
            flex: 1 1 0;
            display: inline-flex;
            flex-direction: column;
            align-items: center;
            gap: 0.15rem;
            padding: 0.4rem 0.2rem;
            background: none;
            border: none;
            cursor: pointer;
            color: #8a8275;
            font-family: inherit;
            font-size: 0.68rem;
            font-weight: 500;
            min-width: 0;
            position: relative;
            transition: color 0.15s;
          }
          .bottom-nav-btn:active { transform: scale(0.96); }
          .bottom-nav-btn.active {
            color: #3d4a2a;
          }
          .bottom-nav-btn .bn-icon-wrap {
            width: 1.65rem;
            height: 1.65rem;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            border-radius: 8px;
            transition: background 0.15s;
          }
          .bottom-nav-btn.active .bn-icon-wrap {
            background: rgba(61, 74, 42, 0.12);
          }
          .bottom-nav-btn .bn-badge {
            position: absolute;
            top: 0.15rem;
            right: 50%;
            transform: translateX(0.55rem);
            background: #c06a43;
            color: #fdfaf3;
            font-size: 0.6rem;
            font-weight: 600;
            min-width: 1rem;
            height: 1rem;
            padding: 0 0.25rem;
            border-radius: 999px;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            line-height: 1;
          }
          /* Inhaltsbereich braucht Padding-Bottom für Bottom-Nav (~70px) */
          main { padding-bottom: calc(70px + env(safe-area-inset-bottom, 0px)) !important; }

          /* ─── Header-Verdichtung ─── */
          .ll-header-title h1 { font-size: 1.25rem !important; }
          .ll-header-subtitle { display: none !important; }
          .ll-header-actions { gap: 0.3rem !important; }
          .ll-header-actions .icon-btn { width: 36px; height: 36px; }

          /* ─── Touch-Target-Mindestgröße für primäre Aktions-Buttons ─── */
          .btn-primary, .btn-secondary {
            min-height: 44px;
            padding: 0.65rem 1.1rem;
          }
          .btn-ghost {
            min-height: 40px;
          }

          /* ─── Bottom-Nav: präzisere Active-Indikation + besserer Lesetext ─── */
          .bottom-nav-btn { font-size: 0.7rem; padding: 0.45rem 0.2rem; }
          .bottom-nav-btn.active::before {
            content: "";
            position: absolute;
            top: 0;
            left: 50%;
            transform: translateX(-50%);
            width: 28px;
            height: 3px;
            background: #c06a43;
            border-radius: 0 0 3px 3px;
          }
          .bottom-nav-btn .bn-icon-wrap {
            transition: background 0.18s ease, transform 0.18s ease;
          }
          .bottom-nav-btn.active .bn-icon-wrap {
            transform: translateY(-1px);
          }

          /* ─── Day-Card-Grid: dichter ─── */
          .ll-daygrid {
            gap: 0.55rem !important;
          }

          /* ─── Touch-Tap-Feedback auf clickable cards ─── */
          .card-clickable:active {
            transform: scale(0.985);
            box-shadow: 0 2px 6px rgba(0,0,0,0.05);
          }

          /* ─── PlanView-Wochenkarte kompakter ─── */
          .ll-week-card {
            padding: 0.7rem 0.85rem !important;
            margin-bottom: 0.85rem !important;
          }
          .ll-week-title { font-size: 1.05rem !important; }
          .ll-week-range { font-size: 0.8rem !important; margin-left: 0.3rem !important; }
          .ll-week-rel { font-size: 0.72rem !important; margin-top: 0.05rem !important; }
          .ll-week-sparkline { margin-top: 0.55rem !important; padding-top: 0.45rem !important; gap: 0.2rem !important; }
          .ll-week-sparkline-cell { max-width: 2.3rem !important; gap: 0.15rem !important; }
          .ll-week-sparkline-day { font-size: 0.6rem !important; }
          .ll-week-sparkline-bar { height: 0.35rem !important; }

          /* ─── Drag-Handle: bessere Touch-Target-Größe auf Mobile ─── */
          .ll-drag-handle {
            width: 2.4rem !important;
            height: 2.4rem !important;
            color: #6b6358 !important;
          }

          /* ─── Toast-Position über Bottom-Nav ─── */
          .ll-toast {
            bottom: calc(72px + env(safe-area-inset-bottom, 0px)) !important;
          }
        }
      `}</style>

      <header
        style={{
          borderBottom: "1px solid #e4dccc",
          background: "#f8f3e8",
          position: "sticky",
          top: 0,
          zIndex: 30,
          backdropFilter: "blur(8px)",
        }}
      >
        <div className="max-w-5xl mx-auto px-5 pt-4 pb-0">
          <div
            style={{
              display: "flex",
              justifyContent: "space-between",
              alignItems: "center",
              gap: "1rem",
              flexWrap: "wrap",
            }}
          >
            <div style={{ display: "flex", alignItems: "center", gap: "0.7rem" }}>
              <LoeffelListeLogo />
              <div className="ll-header-title">
                <h1
                  className="font-display"
                  style={{
                    fontSize: "1.55rem",
                    fontWeight: 600,
                    letterSpacing: "-0.02em",
                    lineHeight: 1,
                    margin: 0,
                  }}
                >
                  Löffelliste
                </h1>
                <div
                  className="ll-header-subtitle"
                  style={{
                    color: "#8a8275",
                    fontSize: "0.75rem",
                    marginTop: "0.2rem",
                    fontFamily: "'Fraunces', serif",
                    fontStyle: "italic",
                    letterSpacing: "0.01em",
                  }}
                >
                  gemeinsam essen planen
                </div>
              </div>
            </div>

            <div className="ll-header-actions" style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
              <button
                className="icon-btn"
                onClick={() => loadFromStorage(false)}
                aria-label="Synchronisieren"
                title={
                  lastSync
                    ? `Zuletzt synchronisiert: ${lastSync.toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" })}`
                    : "Synchronisieren"
                }
              >
                <RefreshCw
                  size={16}
                  style={{
                    animation: syncing ? "mp-spin 0.7s linear infinite" : "none",
                  }}
                />
              </button>
              <button
                className="icon-btn"
                data-tour="help-button"
                onClick={() => setShowHelp(true)}
                aria-label="Hilfe & Anleitung"
                title="Was ist Löffelliste? Anleitung öffnen"
              >
                <HelpCircle size={18} />
              </button>
              {userName && (
                <div style={{ position: "relative" }}>
                  <Avatar
                    name={userName}
                    size={34}
                    title={`${userName} – Profil-Menü öffnen`}
                    onClick={() => setShowProfileMenu((v) => !v)}
                    ring
                  />
                  {showProfileMenu && (
                    <>
                      <div
                        style={{ position: "fixed", inset: 0, zIndex: 40 }}
                        onClick={() => setShowProfileMenu(false)}
                      />
                      <div
                        className="scale-in"
                        style={{
                          position: "absolute",
                          right: 0,
                          top: "100%",
                          marginTop: "0.5rem",
                          minWidth: "15rem",
                          background: "#fdfaf3",
                          border: "1.5px solid #e4dccc",
                          borderRadius: "10px",
                          boxShadow: "0 10px 30px rgba(0,0,0,0.12)",
                          zIndex: 41,
                          overflow: "hidden",
                        }}
                      >
                        <div
                          style={{
                            padding: "0.9rem 1rem",
                            borderBottom: "1px solid #e4dccc",
                            display: "flex",
                            alignItems: "center",
                            gap: "0.6rem",
                          }}
                        >
                          <Avatar name={userName} size={36} />
                          <div style={{ minWidth: 0 }}>
                            <div
                              style={{
                                fontWeight: 600,
                                whiteSpace: "nowrap",
                                overflow: "hidden",
                                textOverflow: "ellipsis",
                              }}
                            >
                              {userName}
                            </div>
                            <div
                              style={{
                                fontSize: "0.75rem",
                                color: "#8a8275",
                                whiteSpace: "nowrap",
                                overflow: "hidden",
                                textOverflow: "ellipsis",
                              }}
                            >
                              {userEmail}
                            </div>
                          </div>
                        </div>
                        <button
                          onClick={() => {
                            setShowProfileMenu(false);
                            setShowNameModal(true);
                          }}
                          style={menuItemStyle}
                        >
                          <Pencil size={15} /> Namen ändern
                        </button>
                        <button
                          onClick={() => {
                            setShowProfileMenu(false);
                            setShowShareInfo(true);
                          }}
                          style={menuItemStyle}
                        >
                          <Users size={15} /> Arbeitsbereich
                        </button>
                        <button onClick={handleLogout} style={{ ...menuItemStyle, color: "#a8422a" }}>
                          <LogOut size={15} /> Abmelden
                        </button>
                      </div>
                    </>
                  )}
                </div>
              )}
            </div>
          </div>

          <nav className="tab-row">
            <TabButton dataTour="tab-plan" active={tab === "plan"} onClick={() => setTab("plan")} icon={<Calendar size={17} />} label="Wochenplan" />
            <TabButton dataTour="tab-recipes" active={tab === "recipes"} onClick={() => setTab("recipes")} icon={<BookOpen size={17} />} label="Rezepte" badge={recipes.length} />
            <TabButton dataTour="tab-shopping" active={tab === "shopping"} onClick={() => setTab("shopping")} icon={<ShoppingCart size={17} />} label="Einkauf" />
            <TabButton active={tab === "cook"} onClick={() => setTab("cook")} icon={<Utensils size={17} />} label="Reste-Koch" />
            <TabButton dataTour="tab-finance" active={tab === "finance"} onClick={() => setTab("finance")} icon={<Wallet size={17} />} label="Kasse" />
            <TabButton active={tab === "history"} onClick={() => { setTab("history"); setViewHistoryWeek(null); }} icon={<HistoryIcon size={17} />} label="Verlauf" />
          </nav>
        </div>
      </header>

      <main className="max-w-5xl mx-auto px-5 py-8">
        {loading ? (
          <div style={{ color: "#8a8275", padding: "3rem 0", textAlign: "center" }}>Lade…</div>
        ) : (
          <>
            {tab === "plan" && (
              <PlanView
                weekMonday={weekMonday}
                setWeekMonday={setWeekMonday}
                plan={currentPlan}
                plans={plans}
                recipes={recipes}
                onSetDay={updateCurrentDay}
                onMoveDay={moveDayPlan}
                onConfirm={confirmWeek}
                onUnconfirm={unconfirmWeek}
                onClear={clearWeek}
                shoppingList={shoppingList}
                onOpenRecipeTab={() => setTab("recipes")}
                onGoToShopping={() => setTab("shopping")}
              />
            )}
            {tab === "recipes" && (
              <RecipesView
                recipes={recipes}
                plans={plans}
                onEdit={(r) => setEditingRecipe(r)}
                onDelete={deleteRecipe}
                onSeed={loadSeedRecipes}
                onToggleFavorite={toggleFavorite}
                onTogglePublic={togglePublic}
                onOpen={(id) => setViewingRecipeId(id)}
                viewingRecipeId={viewingRecipeId}
                onCloseDetail={() => setViewingRecipeId(null)}
                recipeMode={recipeMode}
                setRecipeMode={(m) => {
                  setRecipeMode(m);
                  setViewingRecipeId(null);
                  setViewingPublicRecipeId(null);
                  if (m === "discover" && !publicRecipesLoadedAt) {
                    loadPublicRecipes();
                  }
                }}
                publicRecipes={publicRecipes}
                publicRecipesLoadedAt={publicRecipesLoadedAt}
                onLoadPublic={loadPublicRecipes}
                viewingPublicRecipeId={viewingPublicRecipeId}
                onOpenPublic={(id) => setViewingPublicRecipeId(id)}
                onClosePublicDetail={() => setViewingPublicRecipeId(null)}
                onStartPlanPublic={(r) => setPlanningPublicRecipe({ recipe: r })}
                onImportFromChefkoch={importFromChefkoch}
                onNew={() =>
                  setEditingRecipe({
                    id: uid(),
                    name: "",
                    basePortions: 2,
                    notes: "",
                    preparation: "",
                    sourceUrl: "",
                    sourceAuthor: "",
                    recipeCategory: "main",
                    isPublic: false,
                    ingredients: [{ name: "", amount: "", unit: "g" }],
                  })
                }
              />
            )}
            {tab === "shopping" && (
              <ShoppingView
                plans={plans}
                recipes={recipes}
                groups={shoppingList}
                categories={categories}
                ingredientCategories={ingredientCategories}
                onAssignCategory={assignIngredientCategory}
                weekMonday={weekMonday}
                onUnconfirm={unconfirmWeek}
                onAddPurchase={(weekKeys) =>
                  setEditingPurchase({ week_starts: weekKeys || [] })
                }
                shoppingChecks={shoppingChecks}
                purchasedItems={purchasedItems}
                onToggleCheck={toggleShoppingCheck}
                onMarkPurchased={markItemsPurchased}
                onRestorePurchased={restorePurchasedItem}
                onClearChecks={clearShoppingChecks}
                showToast={showToast}
              />
            )}
            {tab === "cook" && (
              <CookView
                recipes={recipes}
                publicRecipes={publicRecipes}
                publicRecipesLoadedAt={publicRecipesLoadedAt}
                onLoadPublic={loadPublicRecipes}
                onStartPlanPublic={(r) => setPlanningPublicRecipe({ recipe: r })}
                weekMonday={weekMonday}
                plan={currentPlan}
                onPlanDay={updateCurrentDay}
                onGoToShopping={() => setTab("shopping")}
                onOpenRecipe={(id) => {
                  setTab("recipes");
                  setViewingRecipeId(id);
                }}
                showToast={showToast}
              />
            )}
            {tab === "history" && (
              <HistoryView
                plans={plans}
                recipes={recipes}
                viewWeek={viewHistoryWeek}
                setViewWeek={setViewHistoryWeek}
                purchases={purchases}
                members={workspaceMembers}
              />
            )}
            {tab === "finance" && (
              <FinanceView
                purchases={purchases}
                settlements={settlements}
                members={workspaceMembers}
                plans={plans}
                currentUserId={session?.user?.id}
                onAddPurchase={() => setEditingPurchase({})}
                onEditPurchase={(p) => setEditingPurchase(p)}
                onViewPurchase={(p) => setViewingPurchase(p)}
                onDeletePurchase={async (id) => {
                  const ok = await askConfirm({
                    title: "Einkauf löschen?",
                    message: "Dieser Einkauf wird unwiderruflich entfernt.",
                    confirmLabel: "Löschen",
                    danger: true,
                  });
                  if (!ok) return;
                  try {
                    // Quittungs-Foto best-effort mitlöschen
                    const target = purchases.find((p) => p.id === id);
                    if (target?.receipt_path) {
                      try {
                        await db.deleteReceipt(target.receipt_path);
                      } catch (e) {
                        console.warn("Quittung konnte nicht gelöscht werden:", e);
                      }
                    }
                    await db.deletePurchase(id);
                    // Defensive: komplette Liste vom Server neu laden, statt
                    // nur lokal zu filtern. Das deckt RLS-stille Errors und
                    // Race-Conditions ab — sonst könnten gelöschte Käufe
                    // weiter im Saldo erscheinen.
                    try {
                      const fresh = await db.listPurchases(workspace.id);
                      setPurchases(fresh);
                    } catch {
                      setPurchases((prev) => prev.filter((x) => x.id !== id));
                    }
                    showToast("Einkauf gelöscht");
                  } catch (e) {
                    console.error(e);
                    showToast("Löschen fehlgeschlagen", "err");
                  }
                }}
                onSettle={async (s) => {
                  if (!workspace) return;
                  try {
                    const saved = await db.saveSettlement(s, workspace.id);
                    if (saved) {
                      setSettlements((prev) => [saved, ...prev.filter((x) => x.id !== saved.id)]);
                    }
                    // Defensive: Purchases neu laden, damit der Settled-
                    // State sofort korrekt berechnet werden kann
                    try {
                      const fresh = await db.listPurchases(workspace.id);
                      setPurchases(fresh);
                    } catch { /* ignore */ }
                  } catch (e) {
                    console.error(e);
                    showToast("Speichern fehlgeschlagen", "err");
                  }
                }}
                onDeleteSettlement={async (id) => {
                  try {
                    await db.deleteSettlement(id);
                    // Defensive: beides neu laden — sowohl Settlements als
                    // auch Purchases, damit Saldo + Settled-State sicher
                    // synchron sind
                    try {
                      const [st, pu] = await Promise.all([
                        db.listSettlements(workspace.id),
                        db.listPurchases(workspace.id),
                      ]);
                      setSettlements(st);
                      setPurchases(pu);
                    } catch {
                      setSettlements((prev) => prev.filter((x) => x.id !== id));
                    }
                    showToast("Ausgleich entfernt");
                  } catch (e) {
                    console.error(e);
                    showToast("Löschen fehlgeschlagen", "err");
                  }
                }}
                showToast={showToast}
              />
            )}
          </>
        )}
      </main>

      {/* ─── Mobile Bottom Navigation (nur <640px sichtbar) ───────── */}
      <nav className="bottom-nav" aria-label="Hauptnavigation">
        <BottomNavButton
          dataTour="tab-plan"
          active={tab === "plan"}
          onClick={() => setTab("plan")}
          icon={<Calendar size={20} />}
          label="Plan"
        />
        <BottomNavButton
          dataTour="tab-shopping"
          active={tab === "shopping"}
          onClick={() => setTab("shopping")}
          icon={<ShoppingCart size={20} />}
          label="Einkauf"
        />
        <BottomNavButton
          dataTour="tab-recipes"
          active={tab === "recipes"}
          onClick={() => setTab("recipes")}
          icon={<BookOpen size={20} />}
          label="Rezepte"
          badge={recipes.length}
        />
        <BottomNavButton
          dataTour="tab-finance"
          active={tab === "finance"}
          onClick={() => setTab("finance")}
          icon={<Wallet size={20} />}
          label="Kasse"
        />
        <BottomNavButton
          active={tab === "cook" || tab === "history"}
          onClick={() => setShowMoreSheet(true)}
          icon={<MoreHorizontal size={20} />}
          label="Mehr"
        />
      </nav>

      {showMoreSheet && (
        <MoreSheet
          onClose={() => setShowMoreSheet(false)}
          onSelect={(id) => {
            if (id === "history") setViewHistoryWeek(null);
            setTab(id);
          }}
          items={[
            {
              id: "cook",
              icon: <Utensils size={17} />,
              label: "Reste-Koch",
              active: tab === "cook",
            },
            {
              id: "history",
              icon: <HistoryIcon size={17} />,
              label: "Verlauf",
              active: tab === "history",
            },
          ]}
        />
      )}

      {editingPurchase && (
        <PurchaseModal
          purchase={editingPurchase}
          members={workspaceMembers}
          currentUserId={session?.user?.id}
          plans={plans}
          workspaceId={workspace?.id}
          onCancel={() => setEditingPurchase(null)}
          onSave={async (data) => {
            if (!workspace) return;
            try {
              const saved = await db.savePurchase(data, workspace.id);
              if (saved) {
                setPurchases((prev) => {
                  const without = prev.filter((p) => p.id !== saved.id);
                  return [saved, ...without].sort((a, b) =>
                    (b.purchased_at || "").localeCompare(a.purchased_at || "")
                  );
                });
              }
              setEditingPurchase(null);
              showToast(data.id ? "Einkauf aktualisiert" : "Einkauf erfasst");
            } catch (e) {
              console.error(e);
              showToast("Speichern fehlgeschlagen", "err");
            }
          }}
        />
      )}

      {viewingPurchase && (
        <PurchaseModal
          purchase={viewingPurchase}
          members={workspaceMembers}
          currentUserId={session?.user?.id}
          plans={plans}
          workspaceId={workspace?.id}
          readonly
          onCancel={() => setViewingPurchase(null)}
          onSave={() => setViewingPurchase(null)}
        />
      )}

      {editingRecipe && (
        <RecipeEditor
          recipe={editingRecipe}
          recipes={recipes}
          onCancel={() => setEditingRecipe(null)}
          onSave={saveRecipe}
        />
      )}

      {showShareInfo && (
        <WorkspaceDialog
          workspace={workspace}
          members={workspaceMembers}
          currentUserId={session?.user?.id}
          onClose={() => setShowShareInfo(false)}
          onRegenerateInvite={handleRegenerateInvite}
          onLeave={handleLeaveWorkspace}
          onCleanupIngredients={handleCleanupIngredientNames}
          showToast={showToast}
        />
      )}

      {planningPublicRecipe && (
        <PlanPublicRecipeModal
          recipe={planningPublicRecipe.recipe}
          currentMonday={weekMonday}
          plans={plans}
          onCancel={() => setPlanningPublicRecipe(null)}
          onConfirm={async ({ dayKey, portions, weekKey }) => {
            if (weekKey !== currentPlanKey) {
              // Zielwoche setzen, damit der User den eingeplanten Tag direkt sieht
              const [y, m, d] = weekKey.split("-").map(Number);
              setWeekMonday(new Date(y, m - 1, d));
            }
            await planPublicRecipeInDay(
              planningPublicRecipe.recipe,
              dayKey,
              portions,
              weekKey
            );
            setPlanningPublicRecipe(null);
            setViewingPublicRecipeId(null);
            setRecipeMode("own");
            setTab("plan");
          }}
        />
      )}

      {showNameModal && (
        <NameModal
          currentName={userName}
          onSave={saveUserName}
          onCancel={userName ? () => setShowNameModal(false) : null}
        />
      )}

      {showHelp && <HelpModal onClose={() => setShowHelp(false)} userId={session?.user?.id} />}

      <GuidedTour
        userId={session?.user?.id}
        steps={LOEFFELLISTE_TOUR_STEPS}
        enabled={!!session?.user?.id && !!workspace && !needsOnboarding && !!userName && !showNameModal}
      />

      {confirmState && <ConfirmDialog state={confirmState} />}

      {toast && (
        <div
          className="fade-in ll-toast"
          style={{
            position: "fixed",
            bottom: "1.5rem",
            left: "50%",
            transform: "translateX(-50%)",
            background: toast.tone === "err" ? "#a8422a" : "#3d4a2a",
            color: "#f5efe4",
            padding: "0.7rem 1.3rem",
            borderRadius: "8px",
            boxShadow: "0 10px 30px rgba(0,0,0,0.15)",
            fontSize: "0.92rem",
            zIndex: 100,
            display: "flex",
            alignItems: "center",
            gap: "0.5rem",
            maxWidth: "calc(100% - 2rem)",
          }}
        >
          {toast.tone === "err" ? <AlertCircle size={16} /> : <Check size={16} />}
          {toast.msg}
        </div>
      )}
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Löffelliste-Logo
// ────────────────────────────────────────────────────────────────────
function LoeffelListeLogo({ size = 40 }) {
  return (
    <div
      style={{
        width: size,
        height: size,
        borderRadius: "10px",
        background: "linear-gradient(135deg, #3d4a2a 0%, #5e7a4d 100%)",
        display: "inline-flex",
        alignItems: "center",
        justifyContent: "center",
        boxShadow: "0 2px 8px rgba(61, 74, 42, 0.25)",
        flexShrink: 0,
      }}
    >
      <svg
        viewBox="0 0 32 32"
        width={size * 0.62}
        height={size * 0.62}
        fill="none"
        stroke="#f5efe4"
        strokeWidth="2.2"
        strokeLinecap="round"
        strokeLinejoin="round"
      >
        <ellipse cx="16" cy="10" rx="6" ry="7.5" />
        <line x1="16" y1="17.5" x2="16" y2="28" />
      </svg>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Tab Button
// ────────────────────────────────────────────────────────────────────
function TabButton({ active, onClick, icon, label, badge, dataTour }) {
  return (
    <button className={`tab-btn ${active ? "active" : ""}`} onClick={onClick} data-tour={dataTour}>
      {icon}
      <span>{label}</span>
      {typeof badge === "number" && badge > 0 && (
        <span
          style={{
            background: active ? "#c06a43" : "#e4dccc",
            color: active ? "#fdfaf3" : "#6b6358",
            fontSize: "0.72rem",
            padding: "0.08rem 0.45rem",
            borderRadius: "999px",
            fontWeight: 600,
          }}
        >
          {badge}
        </span>
      )}
    </button>
  );
}

// ────────────────────────────────────────────────────────────────────
// Mobile Bottom Navigation
// ────────────────────────────────────────────────────────────────────
function BottomNavButton({ active, onClick, icon, label, badge, dataTour }) {
  return (
    <button
      className={`bottom-nav-btn ${active ? "active" : ""}`}
      onClick={onClick}
      aria-label={label}
      data-tour={dataTour}
    >
      <span className="bn-icon-wrap">{icon}</span>
      <span>{label}</span>
      {typeof badge === "number" && badge > 0 && (
        <span className="bn-badge">{badge > 99 ? "99+" : badge}</span>
      )}
    </button>
  );
}

// Bottom Sheet für die "Mehr"-Tabs (Reste-Koch + Verlauf)
function MoreSheet({ items, onSelect, onClose }) {
  return (
    <div
      onClick={onClose}
      style={{
        position: "fixed",
        inset: 0,
        background: "rgba(42, 38, 32, 0.5)",
        backdropFilter: "blur(2px)",
        WebkitBackdropFilter: "blur(2px)",
        zIndex: 100,
        display: "flex",
        alignItems: "flex-end",
        animation: "fadeIn 0.18s ease-out",
      }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        style={{
          width: "100%",
          background: "#fdfaf3",
          borderTopLeftRadius: "18px",
          borderTopRightRadius: "18px",
          paddingBottom: "max(1rem, env(safe-area-inset-bottom))",
          animation: "slideUp 0.24s cubic-bezier(0.32, 0.72, 0, 1)",
          boxShadow: "0 -8px 32px rgba(0,0,0,0.18)",
        }}
      >
        <style>{`
          @keyframes slideUp {
            from { transform: translateY(100%); }
            to { transform: translateY(0); }
          }
        `}</style>
        <div
          style={{
            display: "flex",
            justifyContent: "center",
            padding: "0.6rem 0 0.3rem",
          }}
        >
          <div
            style={{
              width: "2.5rem",
              height: "0.25rem",
              borderRadius: "999px",
              background: "#bfb5a3",
            }}
          />
        </div>
        <div
          style={{
            padding: "0.4rem 1rem 0.6rem",
            display: "grid",
            gap: "0.35rem",
          }}
        >
          {items.map((it) => (
            <button
              key={it.id}
              onClick={() => {
                onSelect(it.id);
                onClose();
              }}
              style={{
                display: "flex",
                alignItems: "center",
                gap: "0.85rem",
                padding: "0.85rem 1rem",
                background: it.active ? "rgba(61, 74, 42, 0.08)" : "transparent",
                border: "none",
                borderRadius: "10px",
                cursor: "pointer",
                fontFamily: "inherit",
                fontSize: "1rem",
                fontWeight: it.active ? 600 : 500,
                color: it.active ? "#3d4a2a" : "#2a2620",
                textAlign: "left",
                width: "100%",
              }}
            >
              <span
                style={{
                  width: "2rem",
                  height: "2rem",
                  borderRadius: "8px",
                  background: it.active
                    ? "rgba(61, 74, 42, 0.15)"
                    : "rgba(61, 74, 42, 0.06)",
                  color: "#3d4a2a",
                  display: "inline-flex",
                  alignItems: "center",
                  justifyContent: "center",
                  flexShrink: 0,
                }}
              >
                {it.icon}
              </span>
              {it.label}
            </button>
          ))}
        </div>
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Zutaten-Autocomplete (Typeahead)
// ────────────────────────────────────────────────────────────────────
// Verwendet an allen Stellen, an denen eine Zutat eingetippt wird.
// Zeigt während des Tippens passende Vorschläge aus den Rezepten +
// aus der Auto-Kategorie-Knowledge-Base. Bei Auswahl wird der Wert
// übernommen und onChange getriggert. Tastatur-Navigation ↑↓⏎Esc.
function IngredientAutocomplete({
  value,
  onChange,
  onEnter,
  suggestions, // Array von Strings (Zutat-Namen)
  placeholder,
  className = "input-base",
  style,
  inputRef,
  autoFocus,
  id,
  disabled,
}) {
  const [open, setOpen] = useState(false);
  const [highlight, setHighlight] = useState(0);
  const wrapRef = useRef(null);
  const localRef = useRef(null);
  const ref = inputRef || localRef;

  // Liste von Kandidaten: zuerst Rezept-Zutaten, dann Wissensbasis.
  // Dedupliziert auf Stamm-Ebene, Groß-/Kleinschreibung erhalten.
  const candidates = useMemo(() => {
    const seen = new Set();
    const out = [];
    const add = (name) => {
      const key = stemIngredient(name);
      if (!key || seen.has(key)) return;
      seen.add(key);
      out.push(name);
    };
    for (const s of suggestions || []) add(s);
    // Aus der Wissensbasis hinzufügen (kapitalisiert)
    for (const names of Object.values(INGREDIENT_CATEGORY_HINTS)) {
      for (const n of names) {
        add(n.charAt(0).toUpperCase() + n.slice(1));
      }
    }
    return out;
  }, [suggestions]);

  // Gefilterte Vorschläge basierend auf aktueller Eingabe
  const filtered = useMemo(() => {
    const q = (value || "").trim();
    if (!q) return [];
    const qStem = stemIngredient(q);
    const qLower = q.toLowerCase();
    const scored = [];
    for (const name of candidates) {
      const nStem = stemIngredient(name);
      const nLower = name.toLowerCase();
      // Exakt oder Prefix? → top rank
      if (nLower === qLower || nStem === qStem) {
        scored.push({ name, rank: 0 });
        continue;
      }
      if (nLower.startsWith(qLower) || nStem.startsWith(qStem)) {
        scored.push({ name, rank: 1 });
        continue;
      }
      // Enthält Substring?
      if (nLower.includes(qLower) || nStem.includes(qStem)) {
        scored.push({ name, rank: 2 });
        continue;
      }
      // Fuzzy: nur bei ähnlicher Länge & Edit-Distance ≤ 2
      if (Math.abs(nStem.length - qStem.length) <= 3 && qStem.length >= 3) {
        const dist = editDistance(nStem, qStem);
        const maxLen = Math.max(nStem.length, qStem.length);
        const limit = maxLen <= 6 ? 1 : 2;
        if (dist <= limit) scored.push({ name, rank: 3 + dist });
      }
    }
    scored.sort((a, b) => a.rank - b.rank || a.name.localeCompare(b.name, "de"));
    return scored.slice(0, 8).map((s) => s.name);
  }, [value, candidates]);

  // Dropdown schließen, wenn außerhalb geklickt wird
  useEffect(() => {
    if (!open) return;
    const onDocClick = (e) => {
      if (wrapRef.current && !wrapRef.current.contains(e.target)) {
        setOpen(false);
      }
    };
    document.addEventListener("mousedown", onDocClick);
    return () => document.removeEventListener("mousedown", onDocClick);
  }, [open]);

  useEffect(() => {
    setHighlight(0);
  }, [value]);

  const pick = (name) => {
    onChange(name);
    setOpen(false);
    // Fokus zurück ins Feld
    if (ref.current) ref.current.focus();
  };

  const showDropdown = open && filtered.length > 0;

  return (
    <div ref={wrapRef} style={{ position: "relative", minWidth: 0, ...(style || {}) }}>
      <input
        id={id}
        ref={ref}
        className={className}
        style={{ width: "100%" }}
        value={value || ""}
        placeholder={placeholder}
        autoComplete="off"
        disabled={disabled}
        autoFocus={autoFocus}
        onChange={(e) => {
          onChange(e.target.value);
          setOpen(true);
        }}
        onFocus={() => setOpen(true)}
        onKeyDown={(e) => {
          if (!showDropdown) {
            if (e.key === "Enter" && onEnter) {
              e.preventDefault();
              onEnter(value);
            }
            return;
          }
          if (e.key === "ArrowDown") {
            e.preventDefault();
            setHighlight((h) => Math.min(filtered.length - 1, h + 1));
          } else if (e.key === "ArrowUp") {
            e.preventDefault();
            setHighlight((h) => Math.max(0, h - 1));
          } else if (e.key === "Enter") {
            e.preventDefault();
            pick(filtered[highlight]);
            if (onEnter) onEnter(filtered[highlight]);
          } else if (e.key === "Escape") {
            e.preventDefault();
            setOpen(false);
          } else if (e.key === "Tab") {
            // Tab übernimmt aktuelles Highlight
            if (filtered[highlight]) {
              onChange(filtered[highlight]);
              setOpen(false);
            }
          }
        }}
      />
      {showDropdown && (
        <ul
          style={{
            position: "absolute",
            top: "calc(100% + 2px)",
            left: 0,
            right: 0,
            zIndex: 50,
            listStyle: "none",
            margin: 0,
            padding: "0.25rem",
            background: "#fdfaf3",
            border: "1px solid #d9d0be",
            borderRadius: "8px",
            boxShadow: "0 4px 14px rgba(0,0,0,0.08)",
            maxHeight: "14rem",
            overflowY: "auto",
          }}
          role="listbox"
        >
          {filtered.map((name, idx) => (
            <li
              key={name}
              role="option"
              aria-selected={idx === highlight}
              onMouseDown={(e) => {
                e.preventDefault(); // verhindert Blur
                pick(name);
              }}
              onMouseEnter={() => setHighlight(idx)}
              style={{
                padding: "0.45rem 0.6rem",
                borderRadius: "6px",
                cursor: "pointer",
                fontSize: "0.9rem",
                color: "#2a2620",
                background: idx === highlight ? "rgba(61, 74, 42, 0.08)" : "transparent",
              }}
            >
              {name}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Mobile-Detection (für responsive Suggestions: Bottom-Sheet vs. inline)
// ────────────────────────────────────────────────────────────────────
function useIsMobile(breakpoint = 768) {
  const [isMobile, setIsMobile] = useState(() =>
    typeof window !== "undefined" ? window.innerWidth < breakpoint : false
  );
  useEffect(() => {
    const onResize = () => setIsMobile(window.innerWidth < breakpoint);
    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);
  }, [breakpoint]);
  return isMobile;
}

// ────────────────────────────────────────────────────────────────────
// RecipePicker: Bottom-Sheet (Mobile) / Modal (Desktop) zum
// Rezept-Wechseln + Portionen anpassen für einen konkreten Tag.
// ────────────────────────────────────────────────────────────────────
function RecipePicker({
  open,
  onClose,
  currentRecipeId,
  currentPortions,
  recipes,
  onPickRecipe,
  onPortionsChange,
  dayLabel,
  isMobile,
}) {
  const [search, setSearch] = useState("");

  useEffect(() => {
    if (!open) setSearch("");
  }, [open]);

  // Sortierte + gefilterte Liste
  const filtered = useMemo(() => {
    const sorted = [...(recipes || [])].sort((a, b) => {
      if (a.isFavorite !== b.isFavorite) return a.isFavorite ? -1 : 1;
      if ((b.usageCount || 0) !== (a.usageCount || 0))
        return (b.usageCount || 0) - (a.usageCount || 0);
      return a.name.localeCompare(b.name, "de");
    });
    const q = search.trim().toLowerCase();
    if (!q) return sorted;
    return sorted.filter((r) => (r.name || "").toLowerCase().includes(q));
  }, [recipes, search]);

  if (!open) return null;

  const Body = (
    <>
      {/* Portionen-Stepper – nur sichtbar wenn schon ein Rezept gesetzt ist */}
      {currentRecipeId && (
        <div
          style={{
            display: "flex",
            alignItems: "center",
            justifyContent: "space-between",
            gap: "0.6rem",
            padding: "0.7rem 0.85rem",
            background: "rgba(61, 74, 42, 0.06)",
            border: "1px solid #e4dccc",
            borderRadius: "0.55rem",
            marginBottom: "0.8rem",
          }}
        >
          <div
            style={{
              display: "flex",
              alignItems: "center",
              gap: "0.45rem",
              fontSize: "0.9rem",
              color: "#4a443b",
            }}
          >
            <Users size={15} style={{ color: "#6b6358" }} />
            <span style={{ fontWeight: 500 }}>Portionen</span>
          </div>
          <div style={{ display: "flex", alignItems: "center", gap: "0.4rem" }}>
            <button
              onClick={() => onPortionsChange(Math.max(1, currentPortions - 1))}
              aria-label="Weniger Portionen"
              style={{
                width: "2.4rem",
                height: "2.4rem",
                borderRadius: "999px",
                border: "1px solid #d6cdb8",
                background: "#fff",
                color: "#4a443b",
                fontSize: "1.1rem",
                fontWeight: 700,
                cursor: "pointer",
                display: "inline-flex",
                alignItems: "center",
                justifyContent: "center",
                fontFamily: "inherit",
              }}
            >
              −
            </button>
            <div
              style={{
                minWidth: "2.5rem",
                textAlign: "center",
                fontSize: "1.15rem",
                fontWeight: 700,
                color: "#2a2620",
              }}
            >
              {currentPortions}
            </div>
            <button
              onClick={() => onPortionsChange(Math.min(99, currentPortions + 1))}
              aria-label="Mehr Portionen"
              style={{
                width: "2.4rem",
                height: "2.4rem",
                borderRadius: "999px",
                border: "1px solid #d6cdb8",
                background: "#fff",
                color: "#4a443b",
                fontSize: "1.1rem",
                fontWeight: 700,
                cursor: "pointer",
                display: "inline-flex",
                alignItems: "center",
                justifyContent: "center",
                fontFamily: "inherit",
              }}
            >
              +
            </button>
          </div>
        </div>
      )}

      {/* Suchfeld */}
      <div style={{ position: "relative", marginBottom: "0.6rem" }}>
        <Search
          size={14}
          style={{
            position: "absolute",
            left: "0.7rem",
            top: "50%",
            transform: "translateY(-50%)",
            color: "#8a8275",
            pointerEvents: "none",
          }}
        />
        <input
          className="input-base"
          type="text"
          value={search}
          onChange={(e) => setSearch(e.target.value)}
          placeholder="Rezept suchen …"
          style={{
            width: "100%",
            padding: "0.6rem 0.7rem 0.6rem 2rem",
            boxSizing: "border-box",
            fontSize: "0.9rem",
          }}
          autoFocus={!isMobile}
        />
      </div>

      {/* Rezept-Liste */}
      {filtered.length === 0 ? (
        <div
          style={{
            padding: "1.2rem",
            textAlign: "center",
            color: "#8a8275",
            fontSize: "0.88rem",
            background: "rgba(107, 99, 88, 0.04)",
            borderRadius: "0.5rem",
          }}
        >
          {search.trim() ? "Keine Treffer." : "Keine Rezepte vorhanden."}
        </div>
      ) : (
        <div
          style={{
            display: "flex",
            flexDirection: "column",
            gap: "0.35rem",
            maxHeight: isMobile ? "50vh" : "55vh",
            overflowY: "auto",
            paddingRight: "0.15rem",
          }}
        >
          {filtered.map((r) => {
            const cat = getRecipeCategory(r.recipeCategory);
            const active = r.id === currentRecipeId;
            return (
              <button
                key={r.id}
                onClick={() => onPickRecipe(r)}
                style={{
                  display: "flex",
                  alignItems: "center",
                  gap: "0.6rem",
                  padding: "0.55rem 0.7rem",
                  background: active ? "rgba(61, 74, 42, 0.1)" : "#fff",
                  border: "1px solid #e4dccc",
                  borderLeft: `3px solid ${cat?.color || "#c06a43"}`,
                  borderRadius: "0.5rem",
                  cursor: "pointer",
                  textAlign: "left",
                  fontFamily: "inherit",
                  minHeight: "2.6rem",
                }}
              >
                <span style={{ fontSize: "1.1rem", flexShrink: 0 }}>{cat?.icon}</span>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div
                    style={{
                      display: "flex",
                      alignItems: "center",
                      gap: "0.3rem",
                      fontWeight: active ? 700 : 600,
                      color: "#2a2620",
                      fontSize: "0.92rem",
                      lineHeight: 1.2,
                      whiteSpace: "nowrap",
                      overflow: "hidden",
                      textOverflow: "ellipsis",
                    }}
                  >
                    {r.name}
                    {r.isFavorite && (
                      <span style={{ fontSize: "0.7rem", flexShrink: 0 }}>⭐</span>
                    )}
                  </div>
                  {(r.usageCount || 0) > 0 && (
                    <div
                      style={{
                        color: "#8a8275",
                        fontSize: "0.74rem",
                        marginTop: "0.1rem",
                      }}
                    >
                      {r.usageCount}× eingeplant
                    </div>
                  )}
                </div>
                {active && <Check size={15} style={{ color: "#3d4a2a", flexShrink: 0 }} />}
              </button>
            );
          })}
        </div>
      )}
    </>
  );

  if (isMobile) {
    return (
      <div
        onClick={onClose}
        style={{
          position: "fixed",
          inset: 0,
          background: "rgba(42, 38, 32, 0.55)",
          zIndex: 60,
          display: "flex",
          alignItems: "flex-end",
          justifyContent: "center",
          animation: "fadeIn 0.15s ease",
        }}
      >
        <div
          onClick={(e) => e.stopPropagation()}
          style={{
            width: "100%",
            maxWidth: "560px",
            background: "#fdfaf3",
            borderTopLeftRadius: "1.1rem",
            borderTopRightRadius: "1.1rem",
            padding: "0.6rem 1rem 1.4rem",
            maxHeight: "85vh",
            display: "flex",
            flexDirection: "column",
            boxShadow: "0 -4px 24px rgba(0,0,0,0.18)",
          }}
        >
          <div
            style={{
              width: "2.5rem",
              height: "0.25rem",
              background: "#d6cdb8",
              borderRadius: "999px",
              margin: "0 auto 0.7rem",
            }}
          />
          <div
            style={{
              display: "flex",
              alignItems: "center",
              justifyContent: "space-between",
              marginBottom: "0.7rem",
            }}
          >
            <div>
              <div
                className="font-display"
                style={{ fontSize: "1.05rem", fontWeight: 600, color: "#2a2620" }}
              >
                {currentRecipeId ? "Rezept ändern" : "Rezept auswählen"}
              </div>
              <div style={{ fontSize: "0.78rem", color: "#8a8275" }}>
                für {dayLabel}
              </div>
            </div>
            <button
              onClick={onClose}
              aria-label="Schließen"
              style={{
                background: "transparent",
                border: "none",
                padding: "0.4rem",
                cursor: "pointer",
                color: "#8a8275",
              }}
            >
              <X size={18} />
            </button>
          </div>
          <div style={{ overflowY: "auto", flex: 1 }}>{Body}</div>
        </div>
      </div>
    );
  }

  // Desktop: Modal mittig
  return (
    <div
      onClick={onClose}
      style={{
        position: "fixed",
        inset: 0,
        background: "rgba(42, 38, 32, 0.45)",
        zIndex: 60,
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        padding: "1rem",
        animation: "fadeIn 0.15s ease",
      }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        style={{
          width: "100%",
          maxWidth: "480px",
          background: "#fdfaf3",
          borderRadius: "0.9rem",
          padding: "1rem 1.1rem 1.2rem",
          maxHeight: "85vh",
          display: "flex",
          flexDirection: "column",
          boxShadow: "0 12px 48px rgba(0,0,0,0.22)",
        }}
      >
        <div
          style={{
            display: "flex",
            alignItems: "center",
            justifyContent: "space-between",
            marginBottom: "0.7rem",
          }}
        >
          <div>
            <div
              className="font-display"
              style={{ fontSize: "1.05rem", fontWeight: 600, color: "#2a2620" }}
            >
              {currentRecipeId ? "Rezept ändern" : "Rezept auswählen"}
            </div>
            <div style={{ fontSize: "0.78rem", color: "#8a8275" }}>
              für {dayLabel}
            </div>
          </div>
          <button
            onClick={onClose}
            aria-label="Schließen"
            style={{
              background: "transparent",
              border: "none",
              padding: "0.4rem",
              cursor: "pointer",
              color: "#8a8275",
            }}
          >
            <X size={18} />
          </button>
        </div>
        <div style={{ overflowY: "auto", flex: 1 }}>{Body}</div>
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// SmartSuggestions: kontextbewusste Rezeptvorschläge
// Mobile: Bottom-Sheet · Desktop: kompakte Inline-Card
// ────────────────────────────────────────────────────────────────────
function SmartSuggestions({
  open,
  onClose,
  dayKey,
  dayLabel,
  plan,
  plans,
  recipes,
  weekMonday,
  onPick,
  isMobile,
}) {
  const [quickOnly, setQuickOnly] = useState(false);

  const suggestions = useMemo(
    () =>
      getSuggestionsForDay({
        dayKey,
        plan,
        plans,
        recipes,
        weekMonday,
        quickOnly,
      }),
    [dayKey, plan, plans, recipes, weekMonday, quickOnly]
  );

  if (!open) return null;

  const Body = (
    <>
      {/* Filter-Toggle: Alle / Schnell-Essen */}
      <div
        style={{
          display: "flex",
          gap: "0.4rem",
          marginBottom: "0.7rem",
          padding: "0.25rem",
          background: "rgba(107, 99, 88, 0.07)",
          borderRadius: "999px",
        }}
      >
        <button
          onClick={() => setQuickOnly(false)}
          style={{
            flex: 1,
            padding: "0.4rem 0.7rem",
            borderRadius: "999px",
            border: "none",
            background: !quickOnly ? "#fff" : "transparent",
            color: "#4a443b",
            fontSize: "0.82rem",
            fontWeight: !quickOnly ? 600 : 400,
            cursor: "pointer",
            boxShadow: !quickOnly ? "0 1px 3px rgba(0,0,0,0.06)" : "none",
            fontFamily: "inherit",
          }}
        >
          Alle
        </button>
        <button
          onClick={() => setQuickOnly(true)}
          style={{
            flex: 1,
            padding: "0.4rem 0.7rem",
            borderRadius: "999px",
            border: "none",
            background: quickOnly ? "#fff" : "transparent",
            color: "#4a443b",
            fontSize: "0.82rem",
            fontWeight: quickOnly ? 600 : 400,
            cursor: "pointer",
            boxShadow: quickOnly ? "0 1px 3px rgba(0,0,0,0.06)" : "none",
            fontFamily: "inherit",
          }}
          title="Schnelle Rezepte (≤ 6 Zutaten oder mit 🚀 markiert)"
        >
          🚀 Schnell-Essen
        </button>
      </div>

      {suggestions.length === 0 ? (
        <div
          style={{
            padding: "1.2rem",
            textAlign: "center",
            color: "#8a8275",
            fontSize: "0.88rem",
            background: "rgba(107, 99, 88, 0.04)",
            borderRadius: "0.5rem",
          }}
        >
          {quickOnly
            ? 'Keine Schnell-Essen gefunden – versuche "Alle".'
            : "Keine passenden Vorschläge – schau in der Rezept-Liste."}
        </div>
      ) : (
        <div style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
          {suggestions.map(({ recipe, reason }) => {
            const cat = getRecipeCategory(recipe.recipeCategory);
            const quick = isQuickMeal(recipe);
            return (
              <button
                key={recipe.id}
                onClick={() => {
                  onPick(recipe);
                  onClose();
                }}
                style={{
                  display: "flex",
                  alignItems: "center",
                  gap: "0.65rem",
                  padding: "0.7rem 0.8rem",
                  background: "#fff",
                  border: `1px solid #e4dccc`,
                  borderLeft: `3px solid ${cat?.color || "#c06a43"}`,
                  borderRadius: "0.55rem",
                  cursor: "pointer",
                  textAlign: "left",
                  fontFamily: "inherit",
                  minHeight: "3.4rem",
                }}
              >
                <span style={{ fontSize: "1.4rem", flexShrink: 0 }}>{cat?.icon}</span>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div
                    style={{
                      display: "flex",
                      alignItems: "center",
                      gap: "0.3rem",
                      fontWeight: 600,
                      color: "#2a2620",
                      fontSize: "0.92rem",
                      lineHeight: 1.2,
                      whiteSpace: "nowrap",
                      overflow: "hidden",
                      textOverflow: "ellipsis",
                    }}
                  >
                    {recipe.name}
                    {quick && (
                      <span
                        title="Schnell-Essen"
                        style={{ fontSize: "0.72rem", flexShrink: 0 }}
                      >
                        🚀
                      </span>
                    )}
                    {recipe.isFavorite && (
                      <span style={{ fontSize: "0.72rem", flexShrink: 0 }}>⭐</span>
                    )}
                  </div>
                  <div
                    style={{
                      color: "#8a8275",
                      fontSize: "0.78rem",
                      marginTop: "0.15rem",
                      fontStyle: "italic",
                      whiteSpace: "nowrap",
                      overflow: "hidden",
                      textOverflow: "ellipsis",
                    }}
                  >
                    {reason}
                  </div>
                </div>
                <ChevronRight size={16} style={{ color: "#c9c0ad", flexShrink: 0 }} />
              </button>
            );
          })}
        </div>
      )}
    </>
  );

  if (isMobile) {
    // Bottom-Sheet
    return (
      <div
        onClick={onClose}
        style={{
          position: "fixed",
          inset: 0,
          background: "rgba(42, 38, 32, 0.55)",
          zIndex: 60,
          display: "flex",
          alignItems: "flex-end",
          justifyContent: "center",
          animation: "fadeIn 0.15s ease",
        }}
      >
        <div
          onClick={(e) => e.stopPropagation()}
          style={{
            width: "100%",
            maxWidth: "560px",
            background: "#fdfaf3",
            borderTopLeftRadius: "1.1rem",
            borderTopRightRadius: "1.1rem",
            padding: "0.6rem 1rem 1.4rem",
            maxHeight: "82vh",
            overflowY: "auto",
            boxShadow: "0 -4px 24px rgba(0,0,0,0.18)",
          }}
        >
          {/* Drag-Handle */}
          <div
            style={{
              width: "2.5rem",
              height: "0.25rem",
              background: "#d6cdb8",
              borderRadius: "999px",
              margin: "0 auto 0.7rem",
            }}
          />
          <div
            style={{
              display: "flex",
              alignItems: "center",
              justifyContent: "space-between",
              marginBottom: "0.7rem",
            }}
          >
            <div>
              <div
                className="font-display"
                style={{ fontSize: "1.05rem", fontWeight: 600, color: "#2a2620" }}
              >
                ✨ Vorschläge für {dayLabel}
              </div>
              <div style={{ fontSize: "0.78rem", color: "#8a8275" }}>
                Passend zu deiner Woche
              </div>
            </div>
            <button
              onClick={onClose}
              aria-label="Schließen"
              style={{
                background: "transparent",
                border: "none",
                padding: "0.4rem",
                cursor: "pointer",
                color: "#8a8275",
              }}
            >
              <X size={18} />
            </button>
          </div>
          {Body}
        </div>
      </div>
    );
  }

  // Desktop: kompakte Inline-Card
  return (
    <div
      style={{
        marginTop: "0.6rem",
        padding: "0.7rem 0.85rem",
        background: "rgba(192, 106, 67, 0.04)",
        border: "1px solid rgba(192, 106, 67, 0.2)",
        borderRadius: "0.55rem",
      }}
    >
      <div
        style={{
          display: "flex",
          alignItems: "center",
          justifyContent: "space-between",
          marginBottom: "0.55rem",
        }}
      >
        <div style={{ fontSize: "0.82rem", color: "#4a443b", fontWeight: 600 }}>
          ✨ Vorschläge für {dayLabel}
        </div>
        <button
          onClick={onClose}
          aria-label="Schließen"
          style={{
            background: "transparent",
            border: "none",
            padding: "0.2rem",
            cursor: "pointer",
            color: "#8a8275",
          }}
        >
          <X size={14} />
        </button>
      </div>
      {Body}
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Avatar
// ────────────────────────────────────────────────────────────────────
function Avatar({ name, size = 24, title, onClick, ring }) {
  const initials = getInitials(name);
  const color = getAvatarColor(name || "?");
  return (
    <span
      title={title || name || "Unbekannt"}
      onClick={onClick}
      style={{
        display: "inline-flex",
        alignItems: "center",
        justifyContent: "center",
        width: size,
        height: size,
        borderRadius: "50%",
        background: color.bg,
        color: color.fg,
        fontSize: Math.max(9, Math.round(size * 0.42)),
        fontWeight: 600,
        letterSpacing: "-0.02em",
        flexShrink: 0,
        cursor: onClick ? "pointer" : "default",
        boxShadow: ring ? `0 0 0 2px #fdfaf3, 0 0 0 3.5px ${color.bg}` : "none",
        userSelect: "none",
        lineHeight: 1,
      }}
    >
      {initials}
    </span>
  );
}

// ────────────────────────────────────────────────────────────────────
// Plan View
// ────────────────────────────────────────────────────────────────────
function PlanView({
  weekMonday,
  setWeekMonday,
  plan,
  plans,
  recipes,
  onSetDay,
  onMoveDay,
  onConfirm,
  onUnconfirm,
  onClear,
  shoppingList,
  onOpenRecipeTab,
  onGoToShopping,
}) {
  const thisMonday = getMonday();
  const weekNr = getWeekNumber(weekMonday);
  const isThisWeek = toISODate(weekMonday) === toISODate(thisMonday);
  const isFuture = weekMonday > thisMonday;
  const isPast = weekMonday < thisMonday;

  const plannedCount = DAYS.filter((d) => plan.days?.[d.key]?.recipeId).length;

  // ── Drag & Drop Controller ────────────────────────────────────────
  // State liegt im Parent, damit alle DayCards synchron auf den aktiven
  // Drag reagieren (Source-Karte ausgrauen, Hover-Target hervorheben).
  const dayGridRef = useRef(null);
  const [dragState, setDragState] = useState({
    active: false,
    srcKey: null,
    overKey: null,
    pointerX: 0,
    pointerY: 0,
    ghostLabel: "",
    ghostColor: "#c06a43",
  });
  const dragRef = useRef({
    active: false,
    srcKey: null,
    pointerId: null,
    startX: 0,
    startY: 0,
    started: false, // ob Threshold überschritten
  });

  // Findet den DayCard-Container unter einer Bildschirm-Position.
  // Liest data-day-key aus dem nächsten Vorfahren mit dem Attribut.
  const findDayKeyAtPoint = (x, y) => {
    const grid = dayGridRef.current;
    if (!grid) return null;
    const els = document.elementsFromPoint(x, y);
    for (const el of els) {
      if (!grid.contains(el)) continue;
      const card = el.closest("[data-day-key]");
      if (card && grid.contains(card)) {
        return card.getAttribute("data-day-key");
      }
    }
    return null;
  };

  const beginDrag = (srcKey, e) => {
    if (!onMoveDay) return;
    const recipeId = plan.days?.[srcKey]?.recipeId;
    if (!recipeId) return;
    const r =
      recipes.find((x) => x.id === recipeId) ||
      (plan.days[srcKey]?.recipeSnapshot
        ? { name: plan.days[srcKey].recipeSnapshot.name, recipeCategory: plan.days[srcKey].recipeSnapshot.recipeCategory }
        : null);
    const cat = r ? getRecipeCategory(r.recipeCategory) : null;
    dragRef.current = {
      active: true,
      srcKey,
      pointerId: e.pointerId,
      startX: e.clientX,
      startY: e.clientY,
      started: false,
    };
    setDragState({
      active: false, // erst nach Threshold "active" setzen
      srcKey,
      overKey: null,
      pointerX: e.clientX,
      pointerY: e.clientY,
      ghostLabel: r?.name || "",
      ghostColor: cat?.color || "#c06a43",
    });
    // Capture, damit wir alle Pointer-Events bekommen
    try {
      e.currentTarget.setPointerCapture?.(e.pointerId);
    } catch {}
  };

  // Body-Klasse für globale Cursor-/Selection-Styles während aktiven Drags
  useEffect(() => {
    if (dragState.active) {
      document.body.classList.add("ll-dragging");
      return () => document.body.classList.remove("ll-dragging");
    }
  }, [dragState.active]);

  // Globale Pointer-Listener während eines aktiven Drags
  useEffect(() => {
    if (!dragState.srcKey) return;
    const THRESHOLD = 6;

    const onMove = (e) => {
      const d = dragRef.current;
      if (!d.active) return;
      const dx = e.clientX - d.startX;
      const dy = e.clientY - d.startY;
      // Threshold prüfen, dann erst als aktiv markieren
      if (!d.started) {
        if (Math.hypot(dx, dy) < THRESHOLD) return;
        d.started = true;
      }
      // Native Scroll während des Drags unterdrücken
      if (e.cancelable) e.preventDefault();
      const overKey = findDayKeyAtPoint(e.clientX, e.clientY);
      setDragState((s) => ({
        ...s,
        active: true,
        pointerX: e.clientX,
        pointerY: e.clientY,
        overKey,
      }));

      // Auto-Scroll wenn nahe am Viewport-Rand
      const margin = 60;
      if (e.clientY < margin) window.scrollBy({ top: -12, behavior: "auto" });
      else if (e.clientY > window.innerHeight - margin)
        window.scrollBy({ top: 12, behavior: "auto" });
    };

    const onUp = () => {
      const d = dragRef.current;
      if (!d.active) return;
      const { srcKey } = d;
      const overKey = dragState.overKey;
      dragRef.current = { active: false, srcKey: null, pointerId: null, startX: 0, startY: 0, started: false };
      setDragState({ active: false, srcKey: null, overKey: null, pointerX: 0, pointerY: 0, ghostLabel: "", ghostColor: "#c06a43" });
      if (d.started && srcKey && overKey && srcKey !== overKey) {
        onMoveDay?.(srcKey, overKey);
      }
    };

    const onCancel = () => {
      dragRef.current = { active: false, srcKey: null, pointerId: null, startX: 0, startY: 0, started: false };
      setDragState({ active: false, srcKey: null, overKey: null, pointerX: 0, pointerY: 0, ghostLabel: "", ghostColor: "#c06a43" });
    };

    document.addEventListener("pointermove", onMove, { passive: false });
    document.addEventListener("pointerup", onUp);
    document.addEventListener("pointercancel", onCancel);
    return () => {
      document.removeEventListener("pointermove", onMove);
      document.removeEventListener("pointerup", onUp);
      document.removeEventListener("pointercancel", onCancel);
    };
  }, [dragState.srcKey, dragState.overKey, onMoveDay]);


  return (
    <div className="fade-in">
      <div
        className="card ll-week-card"
        style={{
          padding: "1rem 1.1rem",
          marginBottom: "1.25rem",
        }}
      >
        <div
          style={{
            display: "flex",
            alignItems: "center",
            justifyContent: "space-between",
            gap: "0.5rem",
          }}
        >
          <button
            className="icon-btn"
            onClick={() => setWeekMonday(addDays(weekMonday, -7))}
            aria-label="Vorige Woche"
            style={{ flexShrink: 0 }}
          >
            <ChevronLeft size={18} />
          </button>
          <div style={{ flex: 1, minWidth: 0, textAlign: "center" }}>
            <div
              className="font-display ll-week-title"
              style={{
                fontSize: "1.3rem",
                fontWeight: 600,
                lineHeight: 1.15,
                whiteSpace: "nowrap",
                overflow: "hidden",
                textOverflow: "ellipsis",
              }}
            >
              KW {weekNr}
              <span
                className="ll-week-range"
                style={{
                  color: "#8a8275",
                  fontWeight: 400,
                  marginLeft: "0.4rem",
                  fontStyle: "italic",
                  fontSize: "0.95rem",
                }}
              >
                {formatWeekRange(weekMonday)}
              </span>
            </div>
            <div className="ll-week-rel" style={{ color: "#6b6358", fontSize: "0.8rem", marginTop: "0.1rem" }}>
              {isThisWeek && "Diese Woche"}
              {isFuture && "Kommende Woche"}
              {isPast && "Vergangene Woche"}
              {plan.confirmedAt && (
                <span style={{ marginLeft: "0.4rem", color: "#3d4a2a", fontWeight: 500 }}>
                  · ✓ bestätigt
                </span>
              )}
            </div>
          </div>
          <button
            className="icon-btn"
            onClick={() => setWeekMonday(addDays(weekMonday, 7))}
            aria-label="Nächste Woche"
            style={{ flexShrink: 0 }}
          >
            <ChevronRight size={18} />
          </button>
        </div>

        {!isThisWeek && (
          <div style={{ display: "flex", justifyContent: "center", marginTop: "0.7rem" }}>
            <button
              onClick={() => setWeekMonday(getMonday())}
              style={{
                display: "inline-flex",
                alignItems: "center",
                gap: "0.35rem",
                padding: "0.4rem 0.95rem",
                background: "#3d4a2a",
                color: "#f5efe4",
                border: "none",
                borderRadius: "999px",
                fontWeight: 600,
                fontSize: "0.82rem",
                cursor: "pointer",
                boxShadow: "0 1px 3px rgba(61, 74, 42, 0.25)",
                fontFamily: "inherit",
              }}
              title="Zur aktuellen Woche springen"
            >
              <Calendar size={13} /> Zu dieser Woche
            </button>
          </div>
        )}

        {/* Wochen-Sparkline: 7 Punkte für die Belegung der Tage */}
        <div
          className="ll-week-sparkline"
          style={{
            display: "flex",
            justifyContent: "center",
            alignItems: "center",
            gap: "0.35rem",
            marginTop: "0.85rem",
            paddingTop: "0.7rem",
            borderTop: "1px dashed #e4dccc",
          }}
        >
          {DAYS.map((d, i) => {
            const dayDate = addDays(weekMonday, i);
            const isToday =
              toISODate(dayDate) === toISODate(thisMonday) &&
              i === (new Date().getDay() === 0 ? 6 : new Date().getDay() - 1);
            const e = plan.days?.[d.key];
            const r = e?.recipeId
              ? recipes.find((x) => x.id === e.recipeId) ||
                (e.recipeSnapshot
                  ? { recipeCategory: e.recipeSnapshot.recipeCategory || "main" }
                  : null)
              : null;
            const cat = r ? getRecipeCategory(r.recipeCategory) : null;
            return (
              <div
                key={d.key}
                className="ll-week-sparkline-cell"
                title={
                  r
                    ? `${d.label}: ${cat?.label || ""}`
                    : `${d.label}: nichts geplant`
                }
                style={{
                  display: "flex",
                  flexDirection: "column",
                  alignItems: "center",
                  gap: "0.2rem",
                  flex: 1,
                  maxWidth: "3rem",
                }}
              >
                <span
                  className="ll-week-sparkline-day"
                  style={{
                    fontSize: "0.62rem",
                    letterSpacing: "0.05em",
                    color: isToday ? "#c06a43" : "#8a8275",
                    fontWeight: isToday ? 700 : 500,
                    textTransform: "uppercase",
                  }}
                >
                  {d.short}
                </span>
                <span
                  className="ll-week-sparkline-bar"
                  style={{
                    width: "100%",
                    height: "0.45rem",
                    borderRadius: "999px",
                    background: cat ? cat.color : "transparent",
                    border: cat
                      ? "none"
                      : `1px ${isToday ? "solid" : "dashed"} ${
                          isToday ? "#c06a43" : "#d6cdb8"
                        }`,
                    boxShadow: isToday
                      ? "0 0 0 2px rgba(192, 106, 67, 0.18)"
                      : "none",
                  }}
                />
              </div>
            );
          })}
        </div>
      </div>

      {recipes.length === 0 && (
        <div
          className="card"
          style={{
            padding: "1.5rem",
            marginBottom: "1.25rem",
            borderStyle: "dashed",
            textAlign: "center",
          }}
        >
          <Utensils size={28} style={{ color: "#c06a43", margin: "0 auto 0.75rem" }} />
          <div style={{ fontWeight: 600, marginBottom: "0.25rem" }}>Noch keine Rezepte</div>
          <div style={{ color: "#6b6358", marginBottom: "1rem" }}>
            Leg zuerst ein paar Rezepte an, dann kannst du hier deinen Wochenplan zusammenstellen.
          </div>
          <button className="btn-primary" onClick={onOpenRecipeTab}>
            <Plus size={16} /> Rezept anlegen
          </button>
        </div>
      )}

      <div
        ref={dayGridRef}
        className="ll-daygrid"
        style={{ display: "grid", gap: "0.75rem", gridTemplateColumns: "minmax(0, 1fr)" }}
      >
        {DAYS.map((day, i) => {
          const date = addDays(weekMonday, i);
          const entry = plan.days?.[day.key];
          const recipe = entry?.recipeId
            ? recipes.find((r) => r.id === entry.recipeId) ||
              (entry.recipeSnapshot
                ? {
                    id: entry.recipeId,
                    name: entry.recipeSnapshot.name,
                    basePortions: entry.recipeSnapshot.basePortions || 2,
                    ingredients: entry.recipeSnapshot.ingredients || [],
                    recipeCategory: entry.recipeSnapshot.recipeCategory || "main",
                    notes: entry.recipeSnapshot.notes || "",
                    isSnapshot: true,
                    sourceAuthor: entry.recipeSnapshot.sourceAuthor || "",
                  }
                : null)
            : null;
          const isDragSource = dragState.active && dragState.srcKey === day.key;
          const isDragOver =
            dragState.active &&
            dragState.overKey === day.key &&
            dragState.srcKey !== day.key;
          return (
            <DayCard
              key={day.key}
              day={day}
              date={date}
              entry={entry}
              recipe={recipe}
              recipes={recipes}
              plan={plan}
              plans={plans}
              weekMonday={weekMonday}
              disabled={false}
              onSet={(patch) => onSetDay(day.key, patch)}
              onClear={() => onSetDay(day.key, null)}
              onBeginDrag={(e) => beginDrag(day.key, e)}
              isDragSource={isDragSource}
              isDragOver={isDragOver}
              isAnyDragActive={dragState.active}
            />
          );
        })}
      </div>

      {/* Drag-Ghost: folgt dem Pointer während eines aktiven Drags */}
      {dragState.active && (
        <div
          style={{
            position: "fixed",
            left: dragState.pointerX,
            top: dragState.pointerY,
            transform: "translate(-50%, -120%)",
            pointerEvents: "none",
            zIndex: 1000,
            background: "#fdfaf3",
            border: `2px solid ${dragState.ghostColor}`,
            borderRadius: "8px",
            padding: "0.4rem 0.7rem",
            boxShadow: "0 8px 24px rgba(0,0,0,0.18)",
            fontSize: "0.85rem",
            fontWeight: 600,
            color: "#2a2620",
            maxWidth: "16rem",
            whiteSpace: "nowrap",
            overflow: "hidden",
            textOverflow: "ellipsis",
            display: "flex",
            alignItems: "center",
            gap: "0.4rem",
          }}
        >
          <GripVertical size={14} style={{ color: dragState.ghostColor, flexShrink: 0 }} />
          <span style={{ overflow: "hidden", textOverflow: "ellipsis" }}>
            {dragState.ghostLabel}
          </span>
        </div>
      )}

      <div
        className="card"
        style={{
          padding: "1.25rem",
          marginTop: "1.5rem",
          background: plan.confirmedAt ? "rgba(61, 74, 42, 0.05)" : "#fdfaf3",
        }}
      >
        <div
          style={{
            display: "flex",
            justifyContent: "space-between",
            alignItems: "flex-start",
            gap: "1rem",
            flexWrap: "wrap",
          }}
        >
          <div>
            <div
              className="font-display"
              style={{ fontSize: "1.15rem", fontWeight: 600, marginBottom: "0.2rem" }}
            >
              {plannedCount} {plannedCount === 1 ? "Tag" : "Tage"} geplant
            </div>
            <div style={{ color: "#6b6358", fontSize: "0.9rem" }}>
              {(() => {
                const flat = flattenGroups(shoppingList);
                return flat.length > 0
                  ? `${flat.length} verschiedene Zutaten zusammengeführt`
                  : "Wähle Rezepte aus, um die Einkaufsliste zu füllen";
              })()}
            </div>
          </div>
          <div style={{ display: "flex", gap: "0.5rem", flexWrap: "wrap" }}>
            {plannedCount > 0 && (
              <button className="btn-ghost btn-danger" onClick={onClear}>
                <Trash2 size={15} /> Woche leeren
              </button>
            )}
            {plan.confirmedAt ? (
              <button className="btn-primary" onClick={onGoToShopping}>
                <ShoppingCart size={16} /> Einkaufsliste ansehen
              </button>
            ) : (
              <button
                className="btn-primary"
                onClick={onConfirm}
                disabled={plannedCount === 0}
              >
                <Check size={16} /> Woche bestätigen
              </button>
            )}
          </div>
        </div>

        {(() => {
          const flat = flattenGroups(shoppingList);
          if (flat.length === 0) return null;
          return (
            <details style={{ marginTop: "1rem" }}>
              <summary
                style={{
                  cursor: "pointer",
                  color: "#3d4a2a",
                  fontWeight: 500,
                  fontSize: "0.9rem",
                  userSelect: "none",
                }}
              >
                Vorschau Einkaufsliste ({flat.length})
              </summary>
              <div style={{ marginTop: "0.75rem", display: "grid", gap: "0.75rem" }}>
                {shoppingList.map((g) => (
                  <div key={g.category?.id || "uncat"}>
                    <div
                      style={{
                        fontSize: "0.78rem",
                        fontWeight: 600,
                        color: "#6b6358",
                        letterSpacing: "0.05em",
                        textTransform: "uppercase",
                        marginBottom: "0.3rem",
                        display: "flex",
                        alignItems: "center",
                        gap: "0.35rem",
                      }}
                    >
                      <span>{g.category?.icon || "📦"}</span>
                      <span>{g.category?.name || "Ohne Kategorie"}</span>
                    </div>
                    <ul
                      style={{
                        paddingLeft: "0.5rem",
                        margin: 0,
                        listStyle: "none",
                        color: "#4a443b",
                        fontSize: "0.9rem",
                        columns: "2 14rem",
                        columnGap: "1.5rem",
                      }}
                    >
                      {g.items.map((i, idx) => (
                        <li
                          key={idx}
                          style={{ breakInside: "avoid", marginBottom: "0.2rem" }}
                        >
                          {i.amount != null ? (
                            <>
                              <strong>
                                {prettyNumber(smartRoundAmount(i.amount, i.unit))} {i.unit}
                              </strong>{" "}
                              {i.name}
                            </>
                          ) : (
                            <>
                              {i.name}{" "}
                              <span style={{ color: "#8a8275" }}>
                                ({i.unit || "nach Bedarf"})
                              </span>
                            </>
                          )}
                        </li>
                      ))}
                    </ul>
                  </div>
                ))}
              </div>
            </details>
          );
        })()}
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Day Card
// ────────────────────────────────────────────────────────────────────
function DayCard({
  day,
  date,
  entry,
  recipe,
  recipes,
  plan,
  plans,
  weekMonday,
  disabled,
  onSet,
  onClear,
  onBeginDrag,
  isDragSource,
  isDragOver,
  isAnyDragActive,
}) {
  const [expanded, setExpanded] = useState(false);
  const [showNoteEditor, setShowNoteEditor] = useState(false);
  const [noteDraft, setNoteDraft] = useState(entry?.note || "");
  const [showSuggestions, setShowSuggestions] = useState(false);
  const [showTagPicker, setShowTagPicker] = useState(false);
  const [showRecipePicker, setShowRecipePicker] = useState(false);
  const tagPickerRef = useRef(null);
  const autoCloseRef = useRef(null);
  const isMobile = useIsMobile();

  // Sync noteDraft, wenn der entry sich extern ändert (z.B. anderer User speichert)
  useEffect(() => {
    setNoteDraft(entry?.note || "");
  }, [entry?.note]);

  // Outside-Click schließt den Tag-Picker
  useEffect(() => {
    if (!showTagPicker) return;
    const onDocClick = (e) => {
      if (tagPickerRef.current && !tagPickerRef.current.contains(e.target)) {
        setShowTagPicker(false);
      }
    };
    document.addEventListener("mousedown", onDocClick);
    document.addEventListener("touchstart", onDocClick, { passive: true });
    return () => {
      document.removeEventListener("mousedown", onDocClick);
      document.removeEventListener("touchstart", onDocClick);
    };
  }, [showTagPicker]);

  // Cleanup auto-close timer beim Unmount/Toggle
  useEffect(() => {
    return () => {
      if (autoCloseRef.current) clearTimeout(autoCloseRef.current);
    };
  }, []);

  const scheduleAutoClose = () => {
    if (autoCloseRef.current) clearTimeout(autoCloseRef.current);
    autoCloseRef.current = setTimeout(() => {
      setShowTagPicker(false);
    }, 1500);
  };

  const isToday = (() => {
    const today = new Date();
    today.setHours(0, 0, 0, 0);
    const d = new Date(date);
    d.setHours(0, 0, 0, 0);
    return d.getTime() === today.getTime();
  })();

  const tagSet = new Set(Array.isArray(entry?.tags) ? entry.tags : []);
  const cat = recipe ? getRecipeCategory(recipe.recipeCategory) : null;

  // Rotations-Anzahl: wie oft wurde dieses Rezept in den letzten 14 Tagen
  // (+ aktuelle Woche) eingeplant? Nur relevant ab 3.
  const rotationCount = useMemo(
    () =>
      recipe?.id && plans && weekMonday
        ? getRecipeRotationCount(recipe.id, plans, weekMonday)
        : 0,
    [recipe?.id, plans, weekMonday]
  );
  const showRotationHint = rotationCount >= 3;

  const handleRecipeChange = (recipeId) => {
    if (!recipeId) {
      onClear();
      return;
    }
    const r = recipes.find((x) => x.id === recipeId);
    // Wenn es sich beim aktuellen Eintrag um einen Snapshot handelt und der
    // User dieselbe recipeId wieder auswählt: keine Änderung.
    if (recipeId === entry?.recipeId && !r && entry.recipeSnapshot) {
      return;
    }
    const basePortions = r?.basePortions || entry?.recipeSnapshot?.basePortions || 2;
    onSet({ recipeId, portions: entry?.portions || basePortions });
  };

  const handlePortionChange = (p) => {
    const n = Math.max(1, Math.min(99, Number(p) || 1));
    onSet({ portions: n });
  };

  const saveNote = () => {
    const trimmed = noteDraft.trim();
    onSet({ note: trimmed });
    setShowNoteEditor(false);
  };

  const toggleTag = (tagId) => {
    const next = new Set(tagSet);
    if (next.has(tagId)) next.delete(tagId);
    else next.add(tagId);
    onSet({ tags: [...next] });
    if (showTagPicker) scheduleAutoClose();
  };

  return (
    <div
      className="card"
      data-day-key={day.key}
      style={{
        padding: "0.75rem 0.9rem",
        borderLeft: isDragOver
          ? "4px solid #3d4a2a"
          : isToday
          ? "4px solid #c06a43"
          : recipe
          ? `3px solid ${cat?.color || "#c06a43"}`
          : "1px solid #e4dccc",
        boxShadow: isDragOver
          ? "0 0 0 2px #3d4a2a, 0 6px 18px rgba(61, 74, 42, 0.18)"
          : isToday
          ? "0 2px 10px rgba(192, 106, 67, 0.15)"
          : undefined,
        opacity: disabled ? 0.75 : isDragSource ? 0.4 : 1,
        background: isDragOver ? "rgba(61, 74, 42, 0.06)" : undefined,
        transform: isDragSource ? "scale(0.985)" : undefined,
        transition:
          "opacity 0.12s ease, transform 0.12s ease, box-shadow 0.12s ease, border-color 0.12s ease, background 0.12s ease",
        width: "100%",
        maxWidth: "100%",
        minWidth: 0,
        overflow: "hidden",
        position: "relative",
        // Während ein Drag aktiv ist: Text-Selektion auf nicht-Quell-Karten
        // unterdrücken, damit Hover-Auto-Scroll nicht selektiert.
        userSelect: isAnyDragActive ? "none" : undefined,
      }}
    >
      {/* Kopfzeile: Tag · Datum  ——  (optional) Avatar des Einsetzers */}
      <div
        style={{
          display: "flex",
          alignItems: "baseline",
          justifyContent: "space-between",
          gap: "0.5rem",
          marginBottom: "0.45rem",
        }}
      >
        <div
          style={{
            display: "flex",
            alignItems: "baseline",
            gap: "0.4rem",
            minWidth: 0,
            flexWrap: "wrap",
          }}
        >
          <span
            style={{
              fontSize: "0.72rem",
              letterSpacing: "0.1em",
              color: "#8a8275",
              textTransform: "uppercase",
              fontWeight: 600,
            }}
          >
            {day.short}
          </span>
          <span
            className="font-display"
            style={{
              fontSize: "1.05rem",
              fontWeight: 600,
              color: "#2a2620",
              lineHeight: 1,
            }}
          >
            {date.getDate()}.
          </span>
          <span style={{ fontSize: "0.8rem", color: "#8a8275" }}>
            {date.toLocaleDateString("de-DE", { month: "short" })}
          </span>
          {isToday && (
            <span
              style={{
                background: "#c06a43",
                color: "#fff",
                fontSize: "0.62rem",
                fontWeight: 700,
                letterSpacing: "0.08em",
                padding: "0.12rem 0.45rem",
                borderRadius: "999px",
                textTransform: "uppercase",
                marginLeft: "0.2rem",
              }}
            >
              Heute
            </span>
          )}
          {cat && (
            <span
              title={cat.label}
              style={{
                fontSize: "0.95rem",
                lineHeight: 1,
                marginLeft: "0.15rem",
              }}
            >
              {cat.icon}
            </span>
          )}
        </div>

        <div
          style={{
            display: "flex",
            alignItems: "center",
            gap: "0.25rem",
            flexShrink: 0,
          }}
        >
          {entry?.setBy && recipe && (
            <Avatar
              name={entry.setBy}
              size={20}
              title={`Eingetragen von ${entry.setBy}${
                entry.setAt
                  ? ` · ${new Date(entry.setAt).toLocaleDateString("de-DE", {
                      day: "2-digit",
                      month: "2-digit",
                    })}`
                  : ""
              }`}
            />
          )}
          {/* Drag-Handle – nur sichtbar wenn ein Rezept gesetzt ist */}
          {recipe && !disabled && onBeginDrag && (
            <button
              type="button"
              className="ll-drag-handle"
              aria-label={`${recipe.name} verschieben`}
              title="Ziehen, um zu verschieben"
              onPointerDown={(e) => {
                // Nur primärer Button (Maus) oder Touch/Pen
                if (e.pointerType === "mouse" && e.button !== 0) return;
                e.stopPropagation();
                onBeginDrag(e);
              }}
              style={{
                width: "1.9rem",
                height: "1.9rem",
                display: "inline-flex",
                alignItems: "center",
                justifyContent: "center",
                background: "transparent",
                border: "none",
                borderRadius: "0.4rem",
                color: "#8a8275",
                cursor: "grab",
                fontFamily: "inherit",
                touchAction: "none", // wichtig: native Gesten unterdrücken
                WebkitUserSelect: "none",
                userSelect: "none",
              }}
            >
              <GripVertical size={16} />
            </button>
          )}
        </div>
      </div>
      {/* Empty-Mode: Rezept-Picker-Button + ✨ Tipps */}
      {!recipe && (
        <div
          style={{
            display: "flex",
            gap: "0.4rem",
            alignItems: "center",
            flexWrap: "wrap",
          }}
        >
          <button
            onClick={() => !disabled && recipes.length > 0 && setShowRecipePicker(true)}
            disabled={disabled || recipes.length === 0}
            className="input-base"
            style={{
              flex: "1 1 0",
              minWidth: 0,
              maxWidth: "100%",
              boxSizing: "border-box",
              textAlign: "left",
              cursor: disabled || recipes.length === 0 ? "default" : "pointer",
              fontFamily: "inherit",
              color: "#8a8275",
              fontStyle: "italic",
              display: "flex",
              alignItems: "center",
              justifyContent: "space-between",
              gap: "0.5rem",
            }}
            title={recipes.length === 0 ? "Noch keine Rezepte" : "Rezept auswählen"}
          >
            <span>— Kein Gericht —</span>
            {!disabled && recipes.length > 0 && (
              <ChevronRight size={14} style={{ color: "#c9c0ad", flexShrink: 0 }} />
            )}
          </button>

          {!disabled && recipes.length > 0 && (
            <button
              onClick={() => setShowSuggestions(true)}
              title="Vorschläge basierend auf deiner Woche"
              aria-label="Vorschläge anzeigen"
              style={{
                display: "inline-flex",
                alignItems: "center",
                justifyContent: "center",
                gap: "0.25rem",
                padding: "0.4rem 0.65rem",
                background: "rgba(192, 106, 67, 0.1)",
                border: "1px solid rgba(192, 106, 67, 0.3)",
                borderRadius: "8px",
                color: "#c06a43",
                fontSize: "0.78rem",
                fontWeight: 600,
                cursor: "pointer",
                flexShrink: 0,
                fontFamily: "inherit",
                minHeight: "2.1rem",
              }}
            >
              <Sparkles size={13} /> Tipps
            </button>
          )}
        </div>
      )}

      {/* Display-Mode: Rezept als lesbare Headline + Portionen-Sub-Label */}
      {recipe && (
        <div
          style={{
            display: "flex",
            alignItems: "flex-start",
            justifyContent: "space-between",
            gap: "0.5rem",
          }}
        >
          <button
            onClick={() => !disabled && setShowRecipePicker(true)}
            disabled={disabled}
            style={{
              flex: "1 1 0",
              minWidth: 0,
              display: "block",
              textAlign: "left",
              background: "transparent",
              border: "none",
              padding: 0,
              cursor: disabled ? "default" : "pointer",
              fontFamily: "inherit",
              color: "inherit",
            }}
            title={disabled ? recipe.name : "Rezept wechseln"}
          >
            <div
              style={{
                fontSize: "1.02rem",
                fontWeight: 600,
                lineHeight: 1.25,
                color: "#2a2620",
                display: "-webkit-box",
                WebkitLineClamp: 2,
                WebkitBoxOrient: "vertical",
                overflow: "hidden",
                wordBreak: "break-word",
              }}
            >
              {recipe.name}
            </div>
            <div
              style={{
                marginTop: "0.18rem",
                display: "inline-flex",
                alignItems: "center",
                gap: "0.3rem",
                fontSize: "0.82rem",
                color: "#6b6358",
              }}
            >
              <Users size={12} style={{ color: "#8a8275" }} />
              <span>
                <strong style={{ color: "#4a443b", fontWeight: 600 }}>
                  {entry?.portions || recipe.basePortions}
                </strong>{" "}
                Portion{(entry?.portions || recipe.basePortions) === 1 ? "" : "en"}
              </span>
              {recipe.basePortions &&
                (entry?.portions || recipe.basePortions) !== recipe.basePortions && (
                  <span
                    style={{
                      fontSize: "0.72rem",
                      color: "#8a8275",
                      fontStyle: "italic",
                    }}
                  >
                    (Original: {recipe.basePortions})
                  </span>
                )}
            </div>
          </button>

          {/* Inline-Aktionen rechts: Zutaten-Toggle + Entfernen */}
          <div
            style={{
              display: "flex",
              alignItems: "center",
              gap: "0.15rem",
              flexShrink: 0,
            }}
          >
            <button
              className="icon-btn"
              onClick={() => setExpanded((v) => !v)}
              aria-label="Zutaten anzeigen"
              title={expanded ? "Zutaten verbergen" : "Zutaten anzeigen"}
              style={{ width: "28px", height: "28px" }}
            >
              <ChevronRight
                size={15}
                style={{
                  transform: expanded ? "rotate(90deg)" : "rotate(0)",
                  transition: "transform 0.2s",
                }}
              />
            </button>
            {!disabled && (
              <button
                className="icon-btn btn-danger"
                onClick={onClear}
                aria-label="Tag leeren"
                title="Entfernen"
                style={{ width: "28px", height: "28px" }}
              >
                <X size={15} />
              </button>
            )}
          </div>
        </div>
      )}

      {expanded && recipe && (
        <div
          className="scale-in"
          style={{
            marginTop: "0.75rem",
            paddingTop: "0.75rem",
            borderTop: "1px dashed #e4dccc",
            fontSize: "0.88rem",
            color: "#4a443b",
          }}
        >
          <div style={{ display: "flex", flexWrap: "wrap", gap: "0.3rem 1rem" }}>
            {recipe.ingredients.map((ing, idx) => {
              const factor = (entry?.portions || recipe.basePortions) / recipe.basePortions;
              const scaled = (Number(ing.amount) || 0) * factor;
              return (
                <span key={idx}>
                  <strong>
                    {prettyNumber(scaled)} {ing.unit}
                  </strong>{" "}
                  {ing.name}
                </span>
              );
            })}
          </div>
          {recipe.notes && (
            <div
              style={{
                marginTop: "0.5rem",
                color: "#8a8275",
                fontStyle: "italic",
                fontFamily: "'Fraunces', serif",
              }}
            >
              {recipe.notes}
            </div>
          )}
        </div>
      )}

      {/* Aktive Notiz – ruhig & dezent oberhalb der Toolbar (nur wenn vorhanden) */}
      {entry?.note && !showNoteEditor && !disabled && (
        <div
          onClick={() => setShowNoteEditor(true)}
          style={{
            marginTop: "0.55rem",
            padding: "0.4rem 0.6rem",
            background: "rgba(74, 107, 138, 0.06)",
            borderLeft: "2px solid #4a6b8a",
            borderRadius: "0 0.35rem 0.35rem 0",
            fontSize: "0.83rem",
            color: "#4a443b",
            fontStyle: "italic",
            cursor: "pointer",
            whiteSpace: "pre-wrap",
            wordBreak: "break-word",
          }}
          title="Klick zum Bearbeiten"
        >
          {entry.note}
        </div>
      )}

      {/* Notiz-Editor (inline expand) */}
      {showNoteEditor && !disabled && (
        <div style={{ marginTop: "0.55rem" }}>
          <textarea
            className="input-base"
            value={noteDraft}
            onChange={(e) => setNoteDraft(e.target.value)}
            placeholder="z.B. Marie kommt zu Besuch · Reste vom Vortag · 4 Personen"
            rows={2}
            style={{
              width: "100%",
              resize: "vertical",
              fontSize: "0.85rem",
              fontFamily: "inherit",
              padding: "0.5rem 0.6rem",
              boxSizing: "border-box",
            }}
            autoFocus
          />
          <div
            style={{
              display: "flex",
              gap: "0.4rem",
              justifyContent: "flex-end",
              marginTop: "0.35rem",
            }}
          >
            <button
              className="btn-ghost"
              onClick={() => {
                setNoteDraft(entry?.note || "");
                setShowNoteEditor(false);
              }}
              style={{ fontSize: "0.78rem", padding: "0.3rem 0.6rem" }}
            >
              Abbrechen
            </button>
            <button
              className="btn-primary"
              onClick={saveNote}
              style={{ fontSize: "0.78rem", padding: "0.3rem 0.7rem" }}
            >
              <Check size={12} /> Speichern
            </button>
          </div>
        </div>
      )}

      {/* Footer-Toolbar: aktive Tag-Icons links · Tag/Notiz/More-Buttons rechts */}
      {!disabled && (
        <div
          style={{
            marginTop: "0.55rem",
            display: "flex",
            alignItems: "center",
            justifyContent: "space-between",
            gap: "0.5rem",
            position: "relative",
          }}
        >
          {/* Aktive Tags als Icon-Reihe ohne Label */}
          <div
            style={{
              display: "flex",
              alignItems: "center",
              gap: "0.25rem",
              minHeight: "1.6rem",
              flex: 1,
              minWidth: 0,
            }}
          >
            {[...tagSet]
              .map((id) => dayTagMap[id])
              .filter(Boolean)
              .map((t) => (
                <span
                  key={t.id}
                  title={t.label}
                  aria-label={t.label}
                  style={{
                    display: "inline-flex",
                    alignItems: "center",
                    justifyContent: "center",
                    width: "1.5rem",
                    height: "1.5rem",
                    borderRadius: "999px",
                    background: `${t.color}1f`,
                    fontSize: "0.78rem",
                    lineHeight: 1,
                  }}
                >
                  {t.icon}
                </span>
              ))}
          </div>

          {/* Aktion-Icons rechts */}
          <div
            style={{
              display: "flex",
              alignItems: "center",
              gap: "0.15rem",
              flexShrink: 0,
            }}
          >
            <button
              onClick={() => {
                setShowTagPicker((v) => !v);
                if (!showTagPicker) scheduleAutoClose();
              }}
              aria-label={tagSet.size > 0 ? "Tags bearbeiten" : "Tag hinzufügen"}
              title={tagSet.size > 0 ? "Tags bearbeiten" : "Tag hinzufügen"}
              style={{
                position: "relative",
                width: "1.9rem",
                height: "1.9rem",
                display: "inline-flex",
                alignItems: "center",
                justifyContent: "center",
                background: showTagPicker ? "rgba(61, 74, 42, 0.1)" : "transparent",
                border: "none",
                borderRadius: "0.4rem",
                color: tagSet.size > 0 ? "#3d4a2a" : "#8a8275",
                cursor: "pointer",
                fontFamily: "inherit",
              }}
            >
              <Tag size={14} />
              {tagSet.size > 0 && (
                <span
                  aria-hidden="true"
                  style={{
                    position: "absolute",
                    top: "0.2rem",
                    right: "0.2rem",
                    width: "0.45rem",
                    height: "0.45rem",
                    background: "#3d4a2a",
                    borderRadius: "999px",
                  }}
                />
              )}
            </button>

            <button
              onClick={() => {
                setShowNoteEditor((v) => !v);
                setShowTagPicker(false);
              }}
              aria-label={entry?.note ? "Notiz bearbeiten" : "Notiz hinzufügen"}
              title={entry?.note ? "Notiz bearbeiten" : "Notiz hinzufügen"}
              style={{
                position: "relative",
                width: "1.9rem",
                height: "1.9rem",
                display: "inline-flex",
                alignItems: "center",
                justifyContent: "center",
                background: showNoteEditor ? "rgba(74, 107, 138, 0.1)" : "transparent",
                border: "none",
                borderRadius: "0.4rem",
                color: entry?.note ? "#4a6b8a" : "#8a8275",
                cursor: "pointer",
                fontFamily: "inherit",
              }}
            >
              <Pencil size={14} />
              {entry?.note && (
                <span
                  aria-hidden="true"
                  style={{
                    position: "absolute",
                    top: "0.2rem",
                    right: "0.2rem",
                    width: "0.45rem",
                    height: "0.45rem",
                    background: "#4a6b8a",
                    borderRadius: "999px",
                  }}
                />
              )}
            </button>
          </div>

          {/* Tag-Picker als Popover über der Toolbar */}
          {showTagPicker && (
            <div
              ref={tagPickerRef}
              role="dialog"
              aria-label="Tags auswählen"
              style={{
                position: "absolute",
                bottom: "calc(100% + 0.4rem)",
                right: 0,
                zIndex: 5,
                background: "#fdfaf3",
                border: "1px solid #e4dccc",
                borderRadius: "0.6rem",
                padding: "0.5rem",
                boxShadow: "0 6px 20px rgba(42, 38, 32, 0.12)",
                display: "flex",
                flexWrap: "wrap",
                gap: "0.3rem",
                maxWidth: "min(20rem, 90vw)",
              }}
            >
              {DAY_TAGS.map((t) => {
                const active = tagSet.has(t.id);
                return (
                  <button
                    key={t.id}
                    onClick={() => toggleTag(t.id)}
                    title={t.hint}
                    style={{
                      display: "inline-flex",
                      alignItems: "center",
                      gap: "0.3rem",
                      padding: "0.3rem 0.6rem",
                      fontSize: "0.78rem",
                      borderRadius: "999px",
                      border: active ? "none" : "1px solid #d6cdb8",
                      background: active ? `${t.color}22` : "#fff",
                      color: active ? t.color : "#4a443b",
                      fontWeight: active ? 600 : 400,
                      cursor: "pointer",
                      fontFamily: "inherit",
                    }}
                  >
                    <span aria-hidden="true">{t.icon}</span>
                    {t.label}
                    {active && <Check size={11} style={{ marginLeft: "0.1rem" }} />}
                  </button>
                );
              })}
            </div>
          )}
        </div>
      )}


      {/* Rotations-Hint: dezent, nur wenn Rezept gesetzt UND ≥3× im 14-Tage-Fenster */}
      {recipe && showRotationHint && !disabled && (
        <button
          onClick={() => setShowSuggestions(true)}
          style={{
            marginTop: "0.45rem",
            display: "inline-flex",
            alignItems: "center",
            gap: "0.3rem",
            padding: "0.22rem 0.55rem",
            background: "rgba(216, 154, 92, 0.14)",
            border: "1px solid rgba(216, 154, 92, 0.35)",
            borderRadius: "999px",
            color: "#a06a2a",
            fontSize: "0.74rem",
            fontWeight: 500,
            cursor: "pointer",
            fontFamily: "inherit",
          }}
          title="Rezept oft eingeplant – Vorschläge zur Abwechslung"
        >
          🔁 {rotationCount}× in 2 Wochen · Tipps?
        </button>
      )}

      {/* Smart-Suggestions: Bottom-Sheet (Mobile) oder inline (Desktop) */}
      <SmartSuggestions
        open={showSuggestions}
        onClose={() => setShowSuggestions(false)}
        dayKey={day.key}
        dayLabel={day.label}
        plan={plan}
        plans={plans}
        recipes={recipes}
        weekMonday={weekMonday}
        isMobile={isMobile}
        onPick={(r) => {
          const basePortions = r?.basePortions || 2;
          onSet({ recipeId: r.id, portions: entry?.portions || basePortions });
        }}
      />

      {/* Rezept-Picker (Bottom-Sheet auf Mobile, Modal auf Desktop)
          Tap auf den Rezept-Namen öffnet diesen — Wechsel + Portionen ± */}
      <RecipePicker
        open={showRecipePicker}
        onClose={() => setShowRecipePicker(false)}
        currentRecipeId={recipe?.id}
        currentPortions={entry?.portions || recipe?.basePortions || 2}
        recipes={recipes}
        onPickRecipe={(r) => {
          handleRecipeChange(r.id);
          setShowRecipePicker(false);
        }}
        onPortionsChange={handlePortionChange}
        dayLabel={day.label}
        isMobile={isMobile}
      />
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Chefkoch-Suche
// ────────────────────────────────────────────────────────────────────
function ChefkochSearchView({ onImport }) {
  const [query, setQuery] = useState("");
  const [results, setResults] = useState([]);
  const [offset, setOffset] = useState(0);
  const [total, setTotal] = useState(0);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [detail, setDetail] = useState(null); // aktuell geöffnetes Rezept-Detail
  const [detailLoading, setDetailLoading] = useState(false);

  const LIMIT = 20;

  const runSearch = async (q, nextOffset = 0) => {
    if (!q.trim()) return;
    setLoading(true);
    setError(null);
    try {
      const data = await chefkochFetch({
        action: "search",
        q: q.trim(),
        offset: String(nextOffset),
        limit: String(LIMIT),
      });
      // Antwort-Format defensiv parsen (results können {recipe:{}} oder direkt sein)
      const rawList = Array.isArray(data?.results) ? data.results : [];
      const list = rawList.map((r) => (r?.recipe ? r.recipe : r)).filter(Boolean);
      setResults(nextOffset === 0 ? list : [...results, ...list]);
      setOffset(nextOffset);
      setTotal(Number(data?.count) || list.length);
    } catch (e) {
      console.error(e);
      setError(e.message || "Suche fehlgeschlagen");
    } finally {
      setLoading(false);
    }
  };

  const onSubmit = (e) => {
    e?.preventDefault?.();
    runSearch(query, 0);
  };

  const openDetail = async (ck) => {
    setDetail({ ...ck, _loading: true });
    setDetailLoading(true);
    try {
      const full = await chefkochFetch({ action: "recipe", id: String(ck.id) });
      setDetail(full);
    } catch (e) {
      console.error(e);
      setError(e.message || "Rezept konnte nicht geladen werden");
      setDetail(null);
    } finally {
      setDetailLoading(false);
    }
  };

  // Detail-Ansicht
  if (detail) {
    const ings = mapChefkochIngredients(detail);
    const instr = (detail.instructions || "").trim();
    const chefkochUrl = detail.id
      ? `https://www.chefkoch.de/rezepte/${detail.id}/`
      : null;
    return (
      <div className="fade-in">
        <button
          className="btn-ghost"
          onClick={() => setDetail(null)}
          style={{ marginBottom: "0.75rem" }}
        >
          <ChevronLeft size={16} /> Zurück zur Suche
        </button>

        <div className="card" style={{ padding: "1rem 1.1rem", marginBottom: "1rem" }}>
          <h2
            className="font-display"
            style={{ fontSize: "1.4rem", fontWeight: 600, marginBottom: "0.25rem" }}
          >
            {detail.title}
          </h2>
          {detail.subtitle && (
            <div style={{ color: "#6b6358", marginBottom: "0.5rem" }}>{detail.subtitle}</div>
          )}
          <div
            style={{
              display: "flex",
              flexWrap: "wrap",
              gap: "0.4rem",
              color: "#6b6358",
              fontSize: "0.85rem",
              marginBottom: "0.75rem",
            }}
          >
            {detail.owner?.username && (
              <span className="pill" style={{ background: "rgba(107,99,88,0.12)" }}>
                @{detail.owner.username}
              </span>
            )}
            {detail.rating?.rating > 0 && (
              <span className="pill" style={{ background: "rgba(192,106,67,0.14)", color: "#a8422a" }}>
                <Star size={11} /> {Number(detail.rating.rating).toFixed(1)}
                {detail.rating.numVotes ? ` (${detail.rating.numVotes})` : ""}
              </span>
            )}
            {detail.totalTime > 0 && (
              <span className="pill" style={{ background: "rgba(61,74,42,0.1)" }}>
                ⏱ {detail.totalTime} Min
              </span>
            )}
            {detail.servings > 0 && (
              <span className="pill" style={{ background: "rgba(61,74,42,0.1)" }}>
                <Users size={11} /> {detail.servings} P.
              </span>
            )}
          </div>

          <button
            className="btn-primary"
            onClick={() => onImport && onImport(detail)}
            disabled={detailLoading}
            style={{ marginBottom: "0.5rem" }}
          >
            <Plus size={16} /> In meine Rezepte übernehmen
          </button>
        </div>

        {detailLoading && (
          <div style={{ padding: "1rem", color: "#8a8275", textAlign: "center" }}>
            Lade Rezept…
          </div>
        )}

        {!detailLoading && ings.length > 0 && (
          <div className="card" style={{ padding: "1rem 1.1rem", marginBottom: "1rem" }}>
            <h3
              className="font-display"
              style={{ fontSize: "1.1rem", fontWeight: 600, marginBottom: "0.5rem" }}
            >
              Zutaten ({detail.servings || "?"} P.)
            </h3>
            <ul style={{ margin: 0, paddingLeft: "1.1rem", lineHeight: 1.6 }}>
              {ings.map((i, idx) => (
                <li key={idx}>
                  {i.amount > 0 ? `${i.amount} ` : ""}
                  {i.unit ? `${i.unit} ` : ""}
                  {i.name}
                </li>
              ))}
            </ul>
          </div>
        )}

        {!detailLoading && instr && (
          <div className="card" style={{ padding: "1rem 1.1rem" }}>
            <h3
              className="font-display"
              style={{ fontSize: "1.1rem", fontWeight: 600, marginBottom: "0.5rem" }}
            >
              Zubereitung
            </h3>
            <div style={{ whiteSpace: "pre-wrap", lineHeight: 1.6, color: "#2a2620" }}>
              {instr}
            </div>
          </div>
        )}

        {chefkochUrl && (
          <div
            style={{
              textAlign: "center",
              marginTop: "1rem",
              fontSize: "0.8rem",
              color: "#8a8275",
            }}
          >
            <a
              href={chefkochUrl}
              target="_blank"
              rel="noopener noreferrer"
              style={{
                color: "#a8422a",
                textDecoration: "none",
                display: "inline-flex",
                alignItems: "center",
                gap: "0.3rem",
              }}
            >
              <Link2 size={12} /> Original auf chefkoch.de ansehen
            </a>
          </div>
        )}
      </div>
    );
  }

  // Listen-Ansicht
  return (
    <div className="fade-in">
      <div
        className="card"
        style={{
          padding: "1rem 1.1rem",
          marginBottom: "1rem",
          background: "rgba(168,66,42,0.06)",
          borderColor: "rgba(168,66,42,0.2)",
        }}
      >
        <div
          style={{
            display: "flex",
            alignItems: "center",
            gap: "0.5rem",
            marginBottom: "0.25rem",
          }}
        >
          <ChefHat size={18} style={{ color: "#a8422a" }} />
          <strong style={{ color: "#2a2620" }}>Chefkoch-Suche</strong>
        </div>
        <div style={{ color: "#6b6358", fontSize: "0.88rem" }}>
          Durchsuche Chefkoch.de und übernimm Rezepte direkt in deine Sammlung. Vor dem
          Speichern kannst du alles noch anpassen.
        </div>
      </div>

      <form
        onSubmit={onSubmit}
        style={{
          display: "flex",
          gap: "0.5rem",
          marginBottom: "1rem",
          flexWrap: "wrap",
        }}
      >
        <div style={{ position: "relative", flex: "1 1 200px", minWidth: 0 }}>
          <Search
            size={16}
            style={{
              position: "absolute",
              left: "0.7rem",
              top: "50%",
              transform: "translateY(-50%)",
              color: "#8a8275",
              pointerEvents: "none",
            }}
          />
          <input
            className="input-base"
            type="search"
            placeholder="z.B. Lasagne, vegan, Kürbissuppe…"
            value={query}
            onChange={(e) => setQuery(e.target.value)}
            style={{ width: "100%", paddingLeft: "2rem" }}
          />
        </div>
        <button
          type="submit"
          className="btn-primary"
          disabled={loading || !query.trim()}
        >
          {loading ? <RefreshCw size={16} style={{ animation: "mp-spin 1s linear infinite" }} /> : <Search size={16} />}
          Suchen
        </button>
      </form>

      {error && (
        <div
          className="card"
          style={{
            padding: "0.85rem 1rem",
            marginBottom: "1rem",
            borderColor: "#c06a43",
            background: "rgba(192,106,67,0.08)",
            color: "#a8422a",
          }}
        >
          <AlertCircle size={15} style={{ display: "inline", verticalAlign: "middle", marginRight: "0.4rem" }} />
          {error}
          <div style={{ color: "#6b6358", fontSize: "0.82rem", marginTop: "0.3rem" }}>
            Falls die Edge Function <code>chefkoch-proxy</code> noch nicht deployed ist, siehe Setup-Anleitung.
          </div>
        </div>
      )}

      {!loading && results.length === 0 && !error && (
        <div
          className="card"
          style={{
            padding: "1.5rem",
            textAlign: "center",
            color: "#8a8275",
            borderStyle: "dashed",
          }}
        >
          Suchbegriff eingeben, um Chefkoch zu durchsuchen.
        </div>
      )}

      <div style={{ display: "grid", gap: "0.6rem", gridTemplateColumns: "minmax(0, 1fr)" }}>
        {results.map((r) => {
          const rating = r?.rating?.rating || 0;
          const votes = r?.rating?.numVotes || 0;
          return (
            <button
              key={r.id}
              type="button"
              onClick={() => openDetail(r)}
              className="card card-clickable"
              style={{
                padding: "0.7rem 0.85rem",
                textAlign: "left",
                background: "#fdfaf3",
                cursor: "pointer",
                border: "1px solid #e4dccc",
                width: "100%",
                minWidth: 0,
              }}
            >
              <div style={{ minWidth: 0 }}>
                <div
                  style={{
                    fontWeight: 600,
                    color: "#2a2620",
                    marginBottom: "0.15rem",
                    lineHeight: 1.3,
                    overflow: "hidden",
                    textOverflow: "ellipsis",
                    display: "-webkit-box",
                    WebkitLineClamp: 2,
                    WebkitBoxOrient: "vertical",
                  }}
                >
                  {r.title}
                </div>
                {r.subtitle && (
                  <div
                    style={{
                      color: "#6b6358",
                      fontSize: "0.8rem",
                      marginBottom: "0.3rem",
                      overflow: "hidden",
                      textOverflow: "ellipsis",
                      whiteSpace: "nowrap",
                    }}
                  >
                    {r.subtitle}
                  </div>
                )}
                <div
                  style={{
                    display: "flex",
                    flexWrap: "wrap",
                    gap: "0.35rem",
                    fontSize: "0.75rem",
                    color: "#6b6358",
                  }}
                >
                  {rating > 0 && (
                    <span style={{ color: "#a8422a", fontWeight: 600 }}>
                      ★ {rating.toFixed(1)} {votes ? `(${votes})` : ""}
                    </span>
                  )}
                  {r.totalTime > 0 && <span>⏱ {r.totalTime} Min</span>}
                  {r.owner?.username && <span>@{r.owner.username}</span>}
                </div>
              </div>
            </button>
          );
        })}
      </div>

      {results.length > 0 && results.length < total && (
        <div style={{ textAlign: "center", marginTop: "1rem" }}>
          <button
            type="button"
            className="btn-ghost"
            onClick={() => runSearch(query, offset + LIMIT)}
            disabled={loading}
          >
            {loading ? "Lade…" : "Mehr laden"}
            <ChevronDown size={14} />
          </button>
        </div>
      )}

      {results.length > 0 && (
        <div
          style={{
            textAlign: "center",
            marginTop: "0.75rem",
            fontSize: "0.78rem",
            color: "#8a8275",
          }}
        >
          {results.length} von {total} Ergebnissen · Quelle: chefkoch.de
        </div>
      )}
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Recipes View
// ────────────────────────────────────────────────────────────────────
function RecipesView({
  recipes,
  plans,
  onEdit,
  onDelete,
  onNew,
  onSeed,
  onToggleFavorite,
  onTogglePublic,
  onOpen,
  viewingRecipeId,
  onCloseDetail,
  // Entdecken:
  recipeMode,
  setRecipeMode,
  publicRecipes,
  publicRecipesLoadedAt,
  onLoadPublic,
  viewingPublicRecipeId,
  onOpenPublic,
  onClosePublicDetail,
  onStartPlanPublic,
  onImportFromChefkoch,
}) {
  const [search, setSearch] = useState("");
  const [showFavoritesOnly, setShowFavoritesOnly] = useState(false);
  const [categoryFilter, setCategoryFilter] = useState("all");

  const ModeSwitch = (
    <div className="mode-switch">
      <button
        type="button"
        className={`mode-own ${recipeMode === "own" ? "active" : ""}`}
        onClick={() => setRecipeMode("own")}
      >
        <BookOpen size={14} />
        <span className="mode-label-long">Unsere Rezepte</span>
        <span className="mode-label-short">Unsere</span>
      </button>
      <button
        type="button"
        className={`mode-discover ${recipeMode === "discover" ? "active" : ""}`}
        onClick={() => setRecipeMode("discover")}
      >
        <Sparkles size={14} /> Entdecken
      </button>
      <button
        type="button"
        className={`mode-chefkoch ${recipeMode === "chefkoch" ? "active" : ""}`}
        onClick={() => setRecipeMode("chefkoch")}
      >
        <ChefHat size={14} /> Chefkoch
      </button>
    </div>
  );

  // Chefkoch-Modus
  if (recipeMode === "chefkoch") {
    return (
      <>
        {ModeSwitch}
        <ChefkochSearchView onImport={onImportFromChefkoch} />
      </>
    );
  }

  // Entdecken-Modus: komplett eigene View
  if (recipeMode === "discover") {
    return (
      <>
        {ModeSwitch}
        <DiscoverView
          publicRecipes={publicRecipes}
          loadedAt={publicRecipesLoadedAt}
          onRefresh={onLoadPublic}
          onOpen={onOpenPublic}
          viewingId={viewingPublicRecipeId}
          onBack={onClosePublicDetail}
          onStartPlan={onStartPlanPublic}
        />
      </>
    );
  }

  if (viewingRecipeId) {
    const recipe = recipes.find((r) => r.id === viewingRecipeId);
    if (!recipe) {
      return (
        <div className="fade-in">
          <button className="btn-ghost" onClick={onCloseDetail}>
            <ChevronLeft size={16} /> Zurück
          </button>
          <div style={{ padding: "2rem", color: "#8a8275" }}>
            Rezept nicht gefunden.
          </div>
        </div>
      );
    }
    return (
      <RecipeDetailView
        recipe={recipe}
        plans={plans}
        onBack={onCloseDetail}
        onEdit={onEdit}
        onDelete={(id) => {
          onDelete(id);
          onCloseDetail();
        }}
        onToggleFavorite={onToggleFavorite}
        onTogglePublic={onTogglePublic}
      />
    );
  }

  const filtered = recipes
    .filter((r) => r.name.toLowerCase().includes(search.toLowerCase()))
    .filter((r) => !showFavoritesOnly || r.isFavorite)
    .filter(
      (r) => categoryFilter === "all" || (r.recipeCategory || "main") === categoryFilter
    )
    .sort((a, b) => {
      if (a.isFavorite !== b.isFavorite) return a.isFavorite ? -1 : 1;
      if ((b.usageCount || 0) !== (a.usageCount || 0))
        return (b.usageCount || 0) - (a.usageCount || 0);
      return a.name.localeCompare(b.name, "de");
    });

  const favoriteCount = recipes.filter((r) => r.isFavorite).length;

  const usedCategories = RECIPE_CATEGORIES.map((c) => ({
    ...c,
    count: recipes.filter((r) => (r.recipeCategory || "main") === c.id).length,
  })).filter((c) => c.count > 0);

  return (
    <div className="fade-in">
      <div
        style={{
          display: "flex",
          alignItems: "center",
          justifyContent: "space-between",
          gap: "0.6rem",
          marginBottom: "1rem",
          flexWrap: "wrap",
        }}
      >
        {ModeSwitch}
        <button
          className="btn-primary"
          onClick={onNew}
          style={{ flexShrink: 0 }}
        >
          <Plus size={16} /> Neues Rezept
        </button>
      </div>
      <div
        style={{
          display: "flex",
          alignItems: "center",
          gap: "0.5rem",
          marginBottom: "0.85rem",
        }}
      >
        <div style={{ position: "relative", flex: 1, minWidth: 0 }}>
          <Search
            size={16}
            style={{
              position: "absolute",
              left: "0.75rem",
              top: "50%",
              transform: "translateY(-50%)",
              color: "#8a8275",
              pointerEvents: "none",
            }}
          />
          <input
            className="input-base"
            placeholder="Rezepte durchsuchen…"
            value={search}
            onChange={(e) => setSearch(e.target.value)}
            style={{ paddingLeft: "2.25rem", width: "100%", boxSizing: "border-box" }}
          />
        </div>
        {favoriteCount > 0 && (
          <button
            className={showFavoritesOnly ? "btn-primary" : "btn-secondary"}
            onClick={() => setShowFavoritesOnly((v) => !v)}
            style={{ padding: "0.55rem 0.75rem", flexShrink: 0 }}
            title={showFavoritesOnly ? "Alle Rezepte anzeigen" : "Nur Favoriten anzeigen"}
          >
            <Star size={15} fill={showFavoritesOnly ? "currentColor" : "none"} />
            {favoriteCount}
          </button>
        )}
      </div>

      {usedCategories.length > 0 && (
        <div
          style={{
            display: "flex",
            gap: "0.4rem",
            flexWrap: "wrap",
            marginBottom: "1.25rem",
          }}
        >
          <CategoryPill
            label="Alle"
            active={categoryFilter === "all"}
            count={recipes.length}
            onClick={() => setCategoryFilter("all")}
          />
          {usedCategories.map((c) => (
            <CategoryPill
              key={c.id}
              label={c.label}
              icon={c.icon}
              color={c.color}
              count={c.count}
              active={categoryFilter === c.id}
              onClick={() => setCategoryFilter(c.id)}
            />
          ))}
        </div>
      )}

      {filtered.length === 0 ? (
        <div
          className="card"
          style={{
            padding: "2.5rem 1.5rem",
            textAlign: "center",
            borderStyle: "dashed",
          }}
        >
          <BookOpen size={28} style={{ color: "#c06a43", margin: "0 auto 0.75rem" }} />
          <div style={{ fontWeight: 600, marginBottom: "0.25rem" }}>
            {search || categoryFilter !== "all" || showFavoritesOnly
              ? "Keine Treffer"
              : "Noch leer"}
          </div>
          <div style={{ color: "#6b6358", marginBottom: "1rem" }}>
            {search || categoryFilter !== "all" || showFavoritesOnly
              ? "Passe die Filter an oder suche nach etwas anderem."
              : "Leg dein erstes Rezept an – oder starte mit zwei Beispielrezepten."}
          </div>
          {!search && categoryFilter === "all" && !showFavoritesOnly && onSeed && recipes.length === 0 && (
            <button className="btn-secondary" onClick={onSeed}>
              <Sparkles size={15} /> Beispielrezepte hinzufügen
            </button>
          )}
        </div>
      ) : (
        <div
          style={{
            display: "grid",
            gap: "0.75rem",
            gridTemplateColumns: "repeat(auto-fill, minmax(18rem, 1fr))",
          }}
        >
          {filtered.map((r) => (
            <RecipeCard
              key={r.id}
              recipe={r}
              onOpen={onOpen}
              onToggleFavorite={onToggleFavorite}
              plans={plans}
            />
          ))}
        </div>
      )}
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// RecipeCard – Karte im Rezepte-Grid (v2 — Phase 1+2)
// Hero-Icon links · 3 Inline-Chips · 2-3 Zutaten-Preview ·
// „Eingeplant für …"-Bar wenn anwendbar · Stern nur on-hover
// ────────────────────────────────────────────────────────────────────
function RecipeCard({ recipe: r, onOpen, onToggleFavorite, plans }) {
  const cat = getRecipeCategory(r.recipeCategory);
  const fromChefkoch = isFromChefkoch(r);
  const CategoryIcon = getRecipeCategoryIcon(r.recipeCategory);

  const ingCount = (r.ingredients || []).filter((i) => i.name?.trim()).length;
  const cookMin = estimateCookTime(r);
  const planned = useMemo(() => getNextPlannedDay(r.id, plans), [r.id, plans]);
  const lastCooked = useMemo(() => getLastCookedDate(r.id, plans), [r.id, plans]);

  // Top-3 Zutaten als Preview (Namen, kommagetrennt)
  const ingredientPreview = (r.ingredients || [])
    .map((i) => displayIngredientName(i.name || ""))
    .filter(Boolean)
    .slice(0, 3)
    .join(" · ");

  return (
    <div
      className="card card-clickable recipe-card-v2"
      onClick={() => onOpen(r.id)}
      style={{
        padding: "0.85rem 0.95rem",
        display: "flex",
        gap: "0.75rem",
        alignItems: "flex-start",
        position: "relative",
      }}
    >
      {/* Hero-Icon-Tile */}
      <div
        style={{
          width: "3rem",
          height: "3rem",
          borderRadius: "10px",
          background: `${cat.color}1f`,
          color: cat.color,
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          flexShrink: 0,
        }}
        aria-hidden="true"
      >
        <CategoryIcon size={22} strokeWidth={1.75} />
      </div>

      {/* Body */}
      <div style={{ flex: 1, minWidth: 0 }}>
        {/* Optionaler "Eingeplant für …"-Banner */}
        {planned && (
          <div
            style={{
              display: "inline-flex",
              alignItems: "center",
              gap: "0.3rem",
              fontSize: "0.7rem",
              color: "#3d4a2a",
              background: "rgba(61, 74, 42, 0.08)",
              padding: "1px 7px",
              borderRadius: "4px",
              marginBottom: "0.35rem",
              fontWeight: 500,
            }}
            title="Dieses Rezept ist im Wochenplan eingeplant"
          >
            <Calendar size={10} /> Eingeplant {formatPlannedFor(planned)}
          </div>
        )}

        {/* Titel + optional Chefkoch-Mini-Tag */}
        <h3
          className="font-display"
          style={{
            fontSize: "1.05rem",
            fontWeight: 600,
            lineHeight: 1.25,
            margin: "0 0 0.3rem 0",
            color: "#2a2620",
            paddingRight: "1.6rem", // Platz für Stern oben rechts
            wordBreak: "break-word",
          }}
        >
          {r.name || <span style={{ color: "#bfb5a3", fontStyle: "italic" }}>(ohne Titel)</span>}
          {fromChefkoch && (
            <span
              style={{
                marginLeft: "0.4rem",
                fontSize: "0.62rem",
                fontWeight: 500,
                background: "rgba(168, 66, 42, 0.1)",
                color: "#a8422a",
                padding: "1px 5px",
                borderRadius: "3px",
                verticalAlign: "2px",
                letterSpacing: "0.02em",
                fontFamily: "system-ui, sans-serif",
                display: "inline-flex",
                alignItems: "center",
                gap: "2px",
              }}
              title="Aus Chefkoch importiert"
            >
              <ChefHat size={9} /> Chefkoch
            </span>
          )}
        </h3>

        {/* Meta-Chips: Kategorie · Portionen · Zutaten · Zeit · (Letztes Mal | gekocht-Counter) */}
        <div
          style={{
            color: "#6b6358",
            fontSize: "0.78rem",
            display: "flex",
            alignItems: "center",
            gap: "0.45rem",
            flexWrap: "wrap",
            marginBottom: ingredientPreview ? "0.35rem" : 0,
            rowGap: "0.2rem",
          }}
        >
          <span style={{ color: cat.color, fontWeight: 500 }}>{cat.label}</span>
          <span style={{ color: "#c4b9a5" }}>·</span>
          <span style={{ display: "inline-flex", alignItems: "center", gap: "0.2rem" }}>
            <Users size={12} /> {r.basePortions} P.
          </span>
          {ingCount > 0 && (
            <>
              <span style={{ color: "#c4b9a5" }}>·</span>
              <span>{ingCount} Zutaten</span>
            </>
          )}
          <span style={{ color: "#c4b9a5" }}>·</span>
          <span
            style={{ display: "inline-flex", alignItems: "center", gap: "0.2rem", color: "#4a6b8a" }}
            title="Geschätzte Zubereitungszeit (basierend auf Zutatenzahl und Kategorie)"
          >
            <Clock size={12} /> ~{cookMin} Min
          </span>
          {lastCooked && (
            <>
              <span style={{ color: "#c4b9a5" }}>·</span>
              <span style={{ color: "#8a8275" }} title={lastCooked.toLocaleDateString("de-DE")}>
                {formatLastCooked(lastCooked)}
              </span>
            </>
          )}
          {!lastCooked && r.usageCount > 0 && (
            <>
              <span style={{ color: "#c4b9a5" }}>·</span>
              <span
                style={{ display: "inline-flex", alignItems: "center", gap: "0.2rem", color: "#3d4a2a", fontWeight: 500 }}
                title={`${r.usageCount}× eingeplant`}
              >
                <TrendingUp size={12} /> {r.usageCount}× gekocht
              </span>
            </>
          )}
        </div>

        {/* Zutaten-Preview (top 3, eine Zeile, ellipsis) */}
        {ingredientPreview && (
          <div
            style={{
              fontSize: "0.76rem",
              color: "#8a8275",
              lineHeight: 1.35,
              overflow: "hidden",
              textOverflow: "ellipsis",
              whiteSpace: "nowrap",
            }}
            title={ingredientPreview}
          >
            {ingredientPreview}
            {ingCount > 3 && <span style={{ color: "#bfb5a3" }}> · …</span>}
          </div>
        )}
      </div>

      {/* Stern oben rechts (nur on-hover sichtbar wenn leer) */}
      {onToggleFavorite && (
        <button
          className={`icon-btn star-toggle ${r.isFavorite ? "is-filled" : "is-empty"}`}
          onClick={(e) => {
            e.stopPropagation();
            onToggleFavorite(r);
          }}
          aria-label={r.isFavorite ? "Favorit entfernen" : "Als Favorit markieren"}
          title={r.isFavorite ? "Favorit entfernen" : "Als Favorit markieren"}
          style={{
            position: "absolute",
            top: "0.55rem",
            right: "0.55rem",
            width: "1.7rem",
            height: "1.7rem",
            color: r.isFavorite ? "#c06a43" : "#bfb5a3",
            background: "transparent",
          }}
        >
          <Star size={14} fill={r.isFavorite ? "currentColor" : "none"} />
        </button>
      )}
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Category Pill
// ────────────────────────────────────────────────────────────────────
function CategoryPill({ label, icon, color, count, active, onClick }) {
  const baseColor = color || "#3d4a2a";
  return (
    <button
      onClick={onClick}
      style={{
        display: "inline-flex",
        alignItems: "center",
        gap: "0.35rem",
        padding: "0.35rem 0.75rem",
        background: active ? baseColor : `${baseColor}10`,
        color: active ? "#fdfaf3" : baseColor,
        border: `1px solid ${active ? baseColor : `${baseColor}30`}`,
        borderRadius: "999px",
        fontSize: "0.82rem",
        fontWeight: 500,
        cursor: "pointer",
        transition: "all 0.15s",
        fontFamily: "inherit",
      }}
    >
      {icon && <span>{icon}</span>}
      <span>{label}</span>
      {count != null && (
        <span
          style={{
            background: active ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.06)",
            padding: "0.05rem 0.4rem",
            borderRadius: "999px",
            fontSize: "0.72rem",
            fontWeight: 600,
          }}
        >
          {count}
        </span>
      )}
    </button>
  );
}

// ────────────────────────────────────────────────────────────────────
// IngredientSuggestionPills – klickbare Vorschläge aus Rezept-Zutaten
// ────────────────────────────────────────────────────────────────────
function IngredientSuggestionPills({
  suggestions,
  excludeNames,
  filter,
  onPick,
  maxVisible = 12,
  label = "Aus deinen Rezepten",
}) {
  const [expanded, setExpanded] = useState(false);

  const excludedSet = useMemo(() => {
    const s = new Set();
    for (const n of excludeNames || []) {
      const k = canonicalBaseKey(n);
      if (k) s.add(k);
    }
    return s;
  }, [excludeNames]);

  const filterKey = canonicalBaseKey(filter || "");

  const available = useMemo(() => {
    return (suggestions || []).filter((s) => {
      if (excludedSet.has(s.key)) return false;
      if (filterKey && !s.key.includes(filterKey)) return false;
      return true;
    });
  }, [suggestions, excludedSet, filterKey]);

  if (available.length === 0) return null;

  const visible = expanded || filterKey ? available : available.slice(0, maxVisible);
  const hidden = available.length - visible.length;
  const isFiltering = !!filterKey;

  return (
    <div style={{ marginTop: "0.6rem" }}>
      <div
        style={{
          fontSize: "0.7rem",
          letterSpacing: "0.05em",
          color: "#8a8275",
          textTransform: "uppercase",
          fontWeight: 600,
          marginBottom: "0.35rem",
          display: "flex",
          alignItems: "center",
          gap: "0.3rem",
        }}
      >
        <Sparkles size={11} />
        {isFiltering ? `Treffer (${available.length})` : label}
      </div>
      <div style={{ display: "flex", flexWrap: "wrap", gap: "0.3rem", alignItems: "center" }}>
        {visible.map((s, idx) => {
          // Separator direkt vor dem ersten Staple einfügen (nur wenn davor was steht)
          const prev = visible[idx - 1];
          const showSeparator = s.isStaple && prev && !prev.isStaple;
          return (
            <React.Fragment key={s.key}>
              {showSeparator && (
                <span
                  aria-hidden="true"
                  style={{
                    alignSelf: "stretch",
                    width: "1px",
                    background: "#e4dccc",
                    margin: "0 0.15rem",
                  }}
                />
              )}
              <button
                type="button"
                onClick={() => onPick(s.name)}
                title={
                  (s.isStaple
                    ? `Grundzutat · in ${s.count} Rezept${s.count === 1 ? "" : "en"}`
                    : `In ${s.count} Rezept${s.count === 1 ? "" : "en"}`) +
                  (s.variants && s.variants.length > 1
                    ? `\nVarianten: ${s.variants.join(", ")}`
                    : "")
                }
                style={{
                  display: "inline-flex",
                  alignItems: "center",
                  gap: "0.25rem",
                  padding: "0.22rem 0.55rem",
                  background: s.isStaple
                    ? "rgba(107, 99, 88, 0.06)"
                    : "rgba(61, 74, 42, 0.06)",
                  color: s.isStaple ? "#8a8275" : "#3d4a2a",
                  border: `1px solid ${
                    s.isStaple ? "rgba(107, 99, 88, 0.18)" : "rgba(61, 74, 42, 0.2)"
                  }`,
                  borderRadius: "999px",
                  fontSize: "0.82rem",
                  fontFamily: "inherit",
                  cursor: "pointer",
                  transition: "all 0.12s",
                }}
                onMouseEnter={(e) => {
                  e.currentTarget.style.background = s.isStaple
                    ? "rgba(107, 99, 88, 0.14)"
                    : "rgba(61, 74, 42, 0.14)";
                }}
                onMouseLeave={(e) => {
                  e.currentTarget.style.background = s.isStaple
                    ? "rgba(107, 99, 88, 0.06)"
                    : "rgba(61, 74, 42, 0.06)";
                }}
              >
                <Plus size={11} /> {s.name}
              </button>
            </React.Fragment>
          );
        })}
        {hidden > 0 && !isFiltering && (
          <button
            type="button"
            onClick={() => setExpanded(true)}
            style={{
              padding: "0.22rem 0.55rem",
              fontSize: "0.82rem",
              background: "transparent",
              border: "1px dashed #c9c0ad",
              color: "#8a8275",
              borderRadius: "999px",
              cursor: "pointer",
              fontFamily: "inherit",
            }}
          >
            +{hidden} weitere
          </button>
        )}
        {expanded && !isFiltering && available.length > maxVisible && (
          <button
            type="button"
            onClick={() => setExpanded(false)}
            style={{
              padding: "0.22rem 0.55rem",
              fontSize: "0.82rem",
              background: "transparent",
              border: "1px dashed #c9c0ad",
              color: "#8a8275",
              borderRadius: "999px",
              cursor: "pointer",
              fontFamily: "inherit",
            }}
          >
            weniger
          </button>
        )}
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// RecipeDetailView
// ────────────────────────────────────────────────────────────────────
function RecipeDetailView({ recipe, plans, onBack, onEdit, onDelete, onToggleFavorite, onTogglePublic }) {
  const cat = getRecipeCategory(recipe.recipeCategory);

  const history = useMemo(() => {
    const entries = [];
    for (const [weekStart, plan] of Object.entries(plans || {})) {
      for (const [dayKey, dayEntry] of Object.entries(plan.days || {})) {
        if (dayEntry?.recipeId === recipe.id && dayEntry.confirmed) {
          const dayIdx = DAYS.findIndex((d) => d.key === dayKey);
          if (dayIdx === -1) continue;
          const monday = new Date(weekStart);
          const date = addDays(monday, dayIdx);
          entries.push({
            date,
            weekStart,
            portions: dayEntry.portions,
            setBy: dayEntry.setBy,
          });
        }
      }
    }
    return entries.sort((a, b) => b.date - a.date);
  }, [plans, recipe.id]);

  const formatDate = (d) =>
    new Date(d).toLocaleDateString("de-DE", {
      day: "2-digit",
      month: "long",
      year: "numeric",
    });

  return (
    <div className="fade-in">
      <div
        style={{
          display: "flex",
          alignItems: "center",
          justifyContent: "space-between",
          marginBottom: "1rem",
          flexWrap: "wrap",
          gap: "0.5rem",
        }}
      >
        <button className="btn-ghost" onClick={onBack}>
          <ChevronLeft size={16} /> Zurück zu Rezepten
        </button>
        <div style={{ display: "flex", gap: "0.3rem", flexWrap: "wrap" }}>
          {onToggleFavorite && (
            <button
              className="btn-ghost"
              onClick={() => onToggleFavorite(recipe)}
              style={{ color: recipe.isFavorite ? "#c06a43" : "#bfb5a3" }}
            >
              <Star size={16} fill={recipe.isFavorite ? "currentColor" : "none"} />
              {recipe.isFavorite ? "Favorit" : "Als Favorit"}
            </button>
          )}
          {onTogglePublic && !isFromChefkoch(recipe) && (
            <button
              className="btn-ghost"
              onClick={() => onTogglePublic(recipe)}
              style={{ color: recipe.isPublic ? "#4a6b8a" : "#bfb5a3" }}
              title={
                recipe.isPublic
                  ? "Nicht mehr öffentlich teilen"
                  : "Öffentlich mit allen App-Nutzern teilen"
              }
            >
              <Share2 size={15} />
              {recipe.isPublic ? "Öffentlich" : "Teilen"}
            </button>
          )}
          <button className="btn-secondary" onClick={() => onEdit(recipe)}>
            <Pencil size={15} /> Bearbeiten
          </button>
          <button
            className="btn-ghost btn-danger"
            onClick={() => onDelete(recipe.id)}
          >
            <Trash2 size={15} />
          </button>
        </div>
      </div>

      {recipe.isPublic && (
        <div
          style={{
            background: "rgba(74, 107, 138, 0.08)",
            border: "1px solid rgba(74, 107, 138, 0.2)",
            borderRadius: "8px",
            padding: "0.6rem 0.85rem",
            marginBottom: "1rem",
            fontSize: "0.85rem",
            color: "#3d4a2a",
            display: "flex",
            alignItems: "center",
            gap: "0.5rem",
          }}
        >
          <Share2 size={14} style={{ color: "#4a6b8a" }} />
          <span>
            Dieses Rezept ist <strong>öffentlich</strong> – alle App-Nutzer:innen
            finden es im „Entdecken"-Bereich.
          </span>
        </div>
      )}

      <div
        className="card"
        style={{
          padding: "1.5rem",
          marginBottom: "1rem",
          borderLeft: `4px solid ${cat.color}`,
        }}
      >
        <div
          style={{
            display: "flex",
            gap: "0.4rem",
            flexWrap: "wrap",
            marginBottom: "0.75rem",
          }}
        >
          <div
            className="pill"
            style={{
              background: `${cat.color}15`,
              color: cat.color,
            }}
          >
            <span>{cat.icon}</span> {cat.label}
          </div>
          {isFromChefkoch(recipe) && (
            <a
              href={recipe.sourceUrl || "https://www.chefkoch.de"}
              target="_blank"
              rel="noopener noreferrer"
              className="pill"
              style={{
                background: "rgba(168, 66, 42, 0.1)",
                color: "#a8422a",
                textDecoration: "none",
              }}
              title="Originalrezept auf chefkoch.de öffnen"
            >
              <ChefHat size={11} /> Aus Chefkoch <Link2 size={10} />
            </a>
          )}
        </div>
        <h1
          className="font-display"
          style={{
            fontSize: "2rem",
            fontWeight: 600,
            letterSpacing: "-0.01em",
            margin: "0 0 0.5rem 0",
            lineHeight: 1.1,
          }}
        >
          {recipe.name}
        </h1>
        <div
          style={{
            display: "flex",
            gap: "1rem",
            flexWrap: "wrap",
            color: "#6b6358",
            fontSize: "0.9rem",
            marginTop: "0.75rem",
          }}
        >
          <div style={{ display: "flex", alignItems: "center", gap: "0.3rem" }}>
            <Users size={14} /> {recipe.basePortions} Portionen (Basis)
          </div>
          <div style={{ display: "flex", alignItems: "center", gap: "0.3rem" }}>
            <Utensils size={14} /> {recipe.ingredients.length} Zutaten
          </div>
          {recipe.usageCount > 0 && (
            <div
              style={{
                display: "flex",
                alignItems: "center",
                gap: "0.3rem",
                color: "#3d4a2a",
                fontWeight: 500,
              }}
            >
              <TrendingUp size={14} /> {recipe.usageCount}× eingeplant
            </div>
          )}
        </div>
        {recipe.notes && (
          <div
            style={{
              marginTop: "1rem",
              padding: "0.85rem 1rem",
              background: "rgba(192, 106, 67, 0.06)",
              border: "1px solid rgba(192, 106, 67, 0.2)",
              borderRadius: "8px",
              fontFamily: "'Fraunces', serif",
              fontStyle: "italic",
              color: "#4a443b",
              fontSize: "0.95rem",
            }}
          >
            {'„'}{recipe.notes}{'"'}
          </div>
        )}
      </div>

      <div className="card" style={{ padding: "1.25rem", marginBottom: "1rem" }}>
        <h2
          className="font-display"
          style={{
            fontSize: "1.2rem",
            fontWeight: 600,
            margin: "0 0 0.85rem 0",
          }}
        >
          Zutaten
        </h2>
        <ul style={{ listStyle: "none", padding: 0, margin: 0 }}>
          {recipe.ingredients.map((ing, i) => (
            <li
              key={i}
              style={{
                padding: "0.5rem 0",
                display: "flex",
                gap: "0.75rem",
                alignItems: "baseline",
                borderBottom:
                  i < recipe.ingredients.length - 1 ? "1px dashed #e4dccc" : "none",
              }}
            >
              <span
                style={{
                  minWidth: "5rem",
                  fontWeight: 600,
                  color: "#2a2620",
                  fontVariantNumeric: "tabular-nums",
                }}
              >
                {ing.amount ? `${prettyNumber(ing.amount)} ${ing.unit}` : ""}
              </span>
              <span style={{ color: "#4a443b", flex: 1 }}>{ing.name}</span>
            </li>
          ))}
        </ul>
      </div>

      {recipe.preparation && recipe.preparation.trim() && (
        <div className="card" style={{ padding: "1.25rem", marginBottom: "1rem" }}>
          <h2
            className="font-display"
            style={{
              fontSize: "1.2rem",
              fontWeight: 600,
              margin: "0 0 0.85rem 0",
            }}
          >
            Zubereitung
          </h2>
          <div
            style={{
              whiteSpace: "pre-wrap",
              lineHeight: 1.65,
              color: "#2a2620",
              fontSize: "0.95rem",
            }}
          >
            {recipe.preparation}
          </div>
        </div>
      )}

      <div className="card" style={{ padding: "1.25rem", marginBottom: "1rem" }}>
        <h2
          className="font-display"
          style={{
            fontSize: "1.2rem",
            fontWeight: 600,
            margin: "0 0 0.85rem 0",
            display: "flex",
            alignItems: "center",
            gap: "0.5rem",
          }}
        >
          <HistoryIcon size={18} />
          Historie
        </h2>
        {history.length === 0 ? (
          <div style={{ color: "#8a8275", fontSize: "0.9rem", fontStyle: "italic" }}>
            Dieses Rezept wurde noch nie eingeplant.
          </div>
        ) : (
          <ul style={{ listStyle: "none", padding: 0, margin: 0 }}>
            {history.slice(0, 10).map((h, i) => (
              <li
                key={i}
                style={{
                  padding: "0.5rem 0",
                  display: "flex",
                  gap: "0.75rem",
                  alignItems: "center",
                  borderBottom:
                    i < Math.min(history.length, 10) - 1 ? "1px dashed #e4dccc" : "none",
                  fontSize: "0.9rem",
                }}
              >
                <div
                  style={{
                    textAlign: "center",
                    minWidth: "2.5rem",
                    padding: "0.2rem 0.4rem",
                    background: "rgba(61, 74, 42, 0.06)",
                    borderRadius: "6px",
                  }}
                >
                  <div
                    style={{
                      fontSize: "0.62rem",
                      letterSpacing: "0.08em",
                      color: "#8a8275",
                      textTransform: "uppercase",
                      fontWeight: 600,
                    }}
                  >
                    {h.date.toLocaleDateString("de-DE", { weekday: "short" })}
                  </div>
                  <div
                    className="font-display"
                    style={{ fontSize: "1.05rem", fontWeight: 600, lineHeight: 1 }}
                  >
                    {h.date.getDate()}.
                  </div>
                </div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ color: "#2a2620" }}>{formatDate(h.date)}</div>
                  <div style={{ color: "#8a8275", fontSize: "0.78rem" }}>
                    {h.portions} Portionen
                    {h.setBy ? ` · von ${h.setBy}` : ""}
                  </div>
                </div>
              </li>
            ))}
            {history.length > 10 && (
              <li
                style={{
                  padding: "0.75rem 0",
                  textAlign: "center",
                  color: "#8a8275",
                  fontSize: "0.85rem",
                }}
              >
                … +{history.length - 10} weitere
              </li>
            )}
          </ul>
        )}
      </div>

      <div
        className="card"
        style={{
          padding: "1rem 1.25rem",
          marginBottom: "1rem",
          fontSize: "0.85rem",
          color: "#6b6358",
        }}
      >
        <div style={{ display: "grid", gap: "0.6rem" }}>
          {recipe.createdAt && (
            <div style={{ display: "flex", gap: "0.5rem", alignItems: "center" }}>
              <Plus size={14} style={{ color: "#8a8275" }} />
              <span>
                Erstellt am{" "}
                <strong style={{ color: "#2a2620", fontWeight: 500 }}>
                  {formatDate(recipe.createdAt)}
                </strong>
                {recipe.createdBy ? (
                  <>
                    {" "}
                    von{" "}
                    <strong style={{ color: "#2a2620", fontWeight: 500 }}>
                      {recipe.createdBy}
                    </strong>
                  </>
                ) : null}
              </span>
            </div>
          )}
          {recipe.updatedAt && (
            <div style={{ display: "flex", gap: "0.5rem", alignItems: "center" }}>
              <Pencil size={14} style={{ color: "#8a8275" }} />
              <span>
                Zuletzt bearbeitet am{" "}
                <strong style={{ color: "#2a2620", fontWeight: 500 }}>
                  {formatDate(recipe.updatedAt)}
                </strong>
                {recipe.updatedBy ? (
                  <>
                    {" "}
                    von{" "}
                    <strong style={{ color: "#2a2620", fontWeight: 500 }}>
                      {recipe.updatedBy}
                    </strong>
                  </>
                ) : null}
              </span>
            </div>
          )}
          {recipe.sourceUrl && (
            <div style={{ display: "flex", gap: "0.5rem", alignItems: "center" }}>
              <Link2 size={14} style={{ color: "#8a8275", flexShrink: 0 }} />
              <span style={{ minWidth: 0, overflow: "hidden", textOverflow: "ellipsis" }}>
                Quelle:{" "}
                <a
                  href={recipe.sourceUrl}
                  target="_blank"
                  rel="noopener noreferrer"
                  style={{
                    color: "#a8422a",
                    textDecoration: "none",
                    fontWeight: 500,
                  }}
                >
                  {recipe.sourceAuthor || "Original ansehen"}
                </a>
              </span>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Recipe Editor
// ────────────────────────────────────────────────────────────────────
function RecipeEditor({ recipe, recipes, onCancel, onSave }) {
  const [draft, setDraft] = useState(() => ({
    ...recipe,
    ingredients: recipe.ingredients.length
      ? recipe.ingredients
      : [{ name: "", amount: "", unit: "g" }],
  }));

  const updateIng = (i, patch) => {
    setDraft((d) => ({
      ...d,
      ingredients: d.ingredients.map((x, idx) => (idx === i ? { ...x, ...patch } : x)),
    }));
  };
  const addIng = () =>
    setDraft((d) => ({
      ...d,
      ingredients: [...d.ingredients, { name: "", amount: "", unit: "g" }],
    }));
  const removeIng = (i) =>
    setDraft((d) => ({
      ...d,
      ingredients: d.ingredients.filter((_, idx) => idx !== i),
    }));

  // Vorschlags-Liste (eigenes Rezept ausschließen, damit man sich nicht selbst vorschlägt)
  const suggestions = useMemo(() => {
    const others = (recipes || []).filter((r) => r.id !== recipe.id);
    return getIngredientSuggestions(others);
  }, [recipes, recipe.id]);

  const currentIngredientNames = draft.ingredients.map((i) => i.name).filter(Boolean);

  // Klick auf Pill: wenn eine leere Zutaten-Zeile existiert, deren Namen setzen;
  // sonst neue Zeile mit dem Namen anhängen.
  const pickSuggestion = (name) => {
    setDraft((d) => {
      const emptyIdx = d.ingredients.findIndex((i) => !i.name?.trim());
      if (emptyIdx >= 0) {
        return {
          ...d,
          ingredients: d.ingredients.map((x, idx) =>
            idx === emptyIdx ? { ...x, name } : x
          ),
        };
      }
      return {
        ...d,
        ingredients: [...d.ingredients, { name, amount: "", unit: "g" }],
      };
    });
  };

  return (
    <div
      style={{
        position: "fixed",
        inset: 0,
        background: "rgba(42, 38, 32, 0.55)",
        zIndex: 50,
        display: "flex",
        alignItems: "flex-start",
        justifyContent: "center",
        padding: "2rem 1rem",
        overflowY: "auto",
      }}
      onClick={onCancel}
    >
      <div
        className="card scale-in"
        style={{
          maxWidth: "38rem",
          width: "100%",
          padding: "1.5rem",
          background: "#fdfaf3",
          boxShadow: "0 25px 60px rgba(0,0,0,0.25)",
        }}
        onClick={(e) => e.stopPropagation()}
      >
        <div
          style={{
            display: "flex",
            justifyContent: "space-between",
            alignItems: "center",
            marginBottom: "1.25rem",
          }}
        >
          <h2 className="font-display" style={{ fontSize: "1.5rem", fontWeight: 600, margin: 0 }}>
            {recipe.name ? "Rezept bearbeiten" : "Neues Rezept"}
          </h2>
          <button className="btn-ghost" onClick={onCancel}>
            <X size={18} />
          </button>
        </div>

        <div style={{ display: "grid", gap: "0.9rem" }}>
          <div>
            <label style={labelStyle}>Name</label>
            <input
              className="input-base"
              style={{ width: "100%" }}
              value={draft.name}
              onChange={(e) => setDraft({ ...draft, name: e.target.value })}
              placeholder="z.B. Linsen-Curry"
              autoFocus
            />
          </div>

          <div style={{ display: "flex", gap: "0.75rem", flexWrap: "wrap" }}>
            <div style={{ flex: "1 1 12rem", minWidth: "10rem" }}>
              <label style={labelStyle}>Kategorie</label>
              <select
                className="input-base"
                style={{ width: "100%" }}
                value={draft.recipeCategory || "main"}
                onChange={(e) => setDraft({ ...draft, recipeCategory: e.target.value })}
              >
                {RECIPE_CATEGORIES.map((c) => (
                  <option key={c.id} value={c.id}>
                    {c.icon}  {c.label}
                  </option>
                ))}
              </select>
            </div>
            <div style={{ flex: "0 0 auto" }}>
              <label style={labelStyle}>Basis-Portionen</label>
              <input
                className="input-base"
                type="number"
                min="1"
                max="20"
                value={draft.basePortions}
                onChange={(e) => setDraft({ ...draft, basePortions: e.target.value })}
                onFocus={(e) => e.target.select()}
                style={{ width: "6rem" }}
              />
            </div>
          </div>
          <div>
            <label style={labelStyle}>Notiz (optional)</label>
            <input
              className="input-base"
              style={{ width: "100%" }}
              value={draft.notes || ""}
              onChange={(e) => setDraft({ ...draft, notes: e.target.value })}
              placeholder="z.B. Bei Bedarf Kokosmilch dazu · 🚀 für Schnell-Essen"
            />
            <div style={{ fontSize: "0.72rem", color: "#8a8275", marginTop: "0.25rem" }}>
              Tipp: 🚀 oder das Wort "schnell" in der Notiz markiert dieses Rezept
              als Schnell-Essen für Vorschläge.
            </div>
          </div>
          <div>
            <label style={labelStyle}>Zubereitung (optional)</label>
            <textarea
              className="input-base"
              style={{
                width: "100%",
                minHeight: "9rem",
                resize: "vertical",
                fontFamily: "'Instrument Sans', system-ui, sans-serif",
                lineHeight: 1.55,
              }}
              value={draft.preparation || ""}
              onChange={(e) => setDraft({ ...draft, preparation: e.target.value })}
              placeholder="Schritt-für-Schritt-Anleitung zur Zubereitung …"
            />
          </div>

          {/* Öffentlich teilen — bei Chefkoch-Rezepten gesperrt (Urheberrecht) */}
          {isFromChefkoch(draft) ? (
            <div
              style={{
                display: "flex",
                alignItems: "flex-start",
                gap: "0.75rem",
                padding: "0.8rem 0.95rem",
                background: "rgba(168, 66, 42, 0.06)",
                border: "1px solid rgba(168, 66, 42, 0.25)",
                borderRadius: "10px",
              }}
            >
              <ChefHat size={15} style={{ color: "#a8422a", marginTop: "0.15rem", flexShrink: 0 }} />
              <div style={{ flex: 1, minWidth: 0 }}>
                <div
                  style={{
                    fontWeight: 600,
                    color: "#2a2620",
                    fontSize: "0.92rem",
                    marginBottom: "0.2rem",
                  }}
                >
                  Aus Chefkoch importiert
                </div>
                <div style={{ fontSize: "0.82rem", color: "#6b6358", lineHeight: 1.4 }}>
                  Aus urheberrechtlichen Gründen kann dieses Rezept nicht in der
                  Community veröffentlicht werden. Die Quelle bleibt hinterlegt.
                </div>
              </div>
            </div>
          ) : (
            <div
              style={{
                display: "flex",
                alignItems: "flex-start",
                gap: "0.75rem",
                padding: "0.8rem 0.95rem",
                background: draft.isPublic ? "rgba(74, 107, 138, 0.08)" : "rgba(61, 74, 42, 0.04)",
                border: `1px solid ${draft.isPublic ? "rgba(74, 107, 138, 0.3)" : "#e4dccc"}`,
                borderRadius: "10px",
                transition: "background 0.2s, border-color 0.2s",
              }}
            >
              <div style={{ flex: 1, minWidth: 0 }}>
                <label
                  htmlFor="public-toggle"
                  style={{
                    display: "flex",
                    alignItems: "center",
                    gap: "0.4rem",
                    fontWeight: 600,
                    color: "#2a2620",
                    fontSize: "0.92rem",
                    marginBottom: "0.2rem",
                    cursor: "pointer",
                  }}
                >
                  <Share2 size={14} style={{ color: draft.isPublic ? "#4a6b8a" : "#8a8275" }} />
                  Öffentlich mit allen App-Nutzer:innen teilen
                </label>
                <div style={{ fontSize: "0.82rem", color: "#6b6358", lineHeight: 1.4 }}>
                  Das Rezept erscheint im „Entdecken"-Bereich aller Nutzer:innen.
                  Du kannst es jederzeit zurückziehen.
                </div>
              </div>
              {/* iOS-artiger Toggle-Switch */}
              <button
                type="button"
                id="public-toggle"
                role="switch"
                aria-checked={!!draft.isPublic}
                onClick={() => setDraft({ ...draft, isPublic: !draft.isPublic })}
                style={{
                  flexShrink: 0,
                  width: "44px",
                  height: "26px",
                  background: draft.isPublic ? "#4a6b8a" : "#c4bba9",
                  borderRadius: "999px",
                  border: "none",
                  padding: 0,
                  position: "relative",
                  cursor: "pointer",
                  transition: "background 0.2s",
                  marginTop: "0.1rem",
                }}
              >
                <span
                  style={{
                    position: "absolute",
                    top: "3px",
                    left: draft.isPublic ? "21px" : "3px",
                    width: "20px",
                    height: "20px",
                    background: "#fdfaf3",
                    borderRadius: "50%",
                    boxShadow: "0 1px 3px rgba(0,0,0,0.2)",
                    transition: "left 0.2s",
                  }}
                />
              </button>
            </div>
          )}

          <div>
            <label style={labelStyle}>Zutaten</label>
            <div style={{ display: "grid", gap: "0.4rem" }}>
              {draft.ingredients.map((ing, i) => (
                <div
                  key={i}
                  style={{
                    display: "grid",
                    gridTemplateColumns: "5.5rem 5.5rem minmax(0, 1fr) auto",
                    gap: "0.4rem",
                    alignItems: "center",
                  }}
                >
                  <input
                    className="input-base"
                    type="number"
                    step="any"
                    value={ing.amount}
                    placeholder="Menge"
                    onChange={(e) => updateIng(i, { amount: e.target.value })}
                    onFocus={(e) => e.target.select()}
                    style={{ minWidth: 0 }}
                  />
                  <select
                    className="input-base"
                    value={ing.unit}
                    onChange={(e) => updateIng(i, { unit: e.target.value })}
                    style={{ minWidth: 0 }}
                  >
                    {UNIT_OPTIONS.map((u) => (
                      <option key={u} value={u}>
                        {u}
                      </option>
                    ))}
                  </select>
                  <IngredientAutocomplete
                    value={ing.name}
                    onChange={(v) => updateIng(i, { name: v })}
                    suggestions={suggestions.map((s) => s.name)}
                    placeholder="Zutat"
                  />
                  <button
                    className="btn-ghost btn-danger"
                    onClick={() => removeIng(i)}
                    disabled={draft.ingredients.length <= 1}
                    aria-label="Zutat entfernen"
                  >
                    <X size={15} />
                  </button>
                </div>
              ))}
            </div>
            <button className="btn-ghost" onClick={addIng} style={{ marginTop: "0.4rem" }}>
              <Plus size={15} /> Zutat hinzufügen
            </button>

            <IngredientSuggestionPills
              suggestions={suggestions}
              excludeNames={currentIngredientNames}
              onPick={pickSuggestion}
              label="Häufige Zutaten aus anderen Rezepten"
            />
          </div>
        </div>

        <div
          style={{
            display: "flex",
            justifyContent: "flex-end",
            gap: "0.6rem",
            marginTop: "1.5rem",
            paddingTop: "1rem",
            borderTop: "1px solid #e4dccc",
          }}
        >
          <button className="btn-secondary" onClick={onCancel}>
            Abbrechen
          </button>
          <button className="btn-primary" onClick={() => onSave(draft)}>
            <Check size={16} /> Speichern
          </button>
        </div>
      </div>
    </div>
  );
}

const labelStyle = {
  display: "block",
  fontSize: "0.78rem",
  fontWeight: 600,
  color: "#6b6358",
  letterSpacing: "0.05em",
  textTransform: "uppercase",
  marginBottom: "0.3rem",
};

const menuItemStyle = {
  display: "flex",
  alignItems: "center",
  gap: "0.6rem",
  width: "100%",
  padding: "0.65rem 1rem",
  background: "transparent",
  border: "none",
  cursor: "pointer",
  textAlign: "left",
  fontSize: "0.9rem",
  color: "#2a2620",
  transition: "background 0.12s",
  fontFamily: "inherit",
};

// ────────────────────────────────────────────────────────────────────
// Workspace-Dialog (Einstellungen: Name, Invite-Code, Mitglieder)
// ────────────────────────────────────────────────────────────────────
function WorkspaceDialog({
  workspace,
  members,
  currentUserId,
  onClose,
  onRegenerateInvite,
  onLeave,
  onCleanupIngredients,
  showToast,
}) {
  const [copied, setCopied] = useState(false);
  const [regenConfirm, setRegenConfirm] = useState(false);

  if (!workspace) return null;

  const inviteLink = `${window.location.origin}${window.location.pathname}#join=${encodeURIComponent(
    workspace.inviteCode
  )}`;

  const copyInvite = async (text) => {
    try {
      if (navigator.clipboard && window.isSecureContext) {
        await navigator.clipboard.writeText(text);
      } else {
        const ta = document.createElement("textarea");
        ta.value = text;
        ta.style.position = "fixed";
        ta.style.opacity = "0";
        document.body.appendChild(ta);
        ta.select();
        document.execCommand("copy");
        document.body.removeChild(ta);
      }
      setCopied(true);
      setTimeout(() => setCopied(false), 1800);
      showToast && showToast("In Zwischenablage kopiert");
    } catch (e) {
      console.error(e);
      showToast && showToast("Kopieren fehlgeschlagen", "err");
    }
  };

  const isOwner = workspace.role === "owner";

  return (
    <div
      style={{
        position: "fixed",
        inset: 0,
        background: "rgba(42, 38, 32, 0.55)",
        zIndex: 50,
        display: "flex",
        alignItems: "flex-start",
        justifyContent: "center",
        padding: "2rem 1rem",
        overflowY: "auto",
      }}
      onClick={onClose}
    >
      <div
        className="card scale-in"
        style={{
          maxWidth: "34rem",
          width: "100%",
          padding: "1.5rem",
          background: "#fdfaf3",
          boxShadow: "0 25px 60px rgba(0,0,0,0.25)",
        }}
        onClick={(e) => e.stopPropagation()}
      >
        <div
          style={{
            display: "flex",
            justifyContent: "space-between",
            alignItems: "center",
            marginBottom: "1.25rem",
          }}
        >
          <h2
            className="font-display"
            style={{
              fontSize: "1.4rem",
              fontWeight: 600,
              margin: 0,
              display: "flex",
              alignItems: "center",
              gap: "0.5rem",
            }}
          >
            <Users size={20} style={{ color: "#3d4a2a" }} />
            Arbeitsbereich
          </h2>
          <button className="btn-ghost" onClick={onClose}>
            <X size={18} />
          </button>
        </div>

        {/* Name + Rolle */}
        <div
          style={{
            background: "rgba(61, 74, 42, 0.06)",
            border: "1px solid rgba(61, 74, 42, 0.15)",
            borderRadius: "10px",
            padding: "0.9rem 1rem",
            marginBottom: "1.2rem",
          }}
        >
          <div style={{ fontSize: "0.75rem", letterSpacing: "0.08em", color: "#8a8275", textTransform: "uppercase", fontWeight: 600, marginBottom: "0.2rem" }}>
            Dein Arbeitsbereich
          </div>
          <div
            className="font-display"
            style={{ fontSize: "1.2rem", fontWeight: 600, color: "#2a2620" }}
          >
            {workspace.name}
          </div>
          <div style={{ fontSize: "0.82rem", color: "#6b6358", marginTop: "0.2rem" }}>
            Du bist {isOwner ? "Besitzer:in" : "Mitglied"} · {members.length} Mitglied{members.length === 1 ? "" : "er"}
          </div>
        </div>

        {/* Invite-Code */}
        <div style={{ marginBottom: "1.4rem" }}>
          <h3
            className="font-display"
            style={{ fontSize: "1.05rem", fontWeight: 600, margin: "0 0 0.5rem" }}
          >
            Jemanden einladen
          </h3>
          <div style={{ fontSize: "0.88rem", color: "#6b6358", marginBottom: "0.7rem" }}>
            Teile diesen Code oder den Link. Wer damit registriert oder beitritt,
            landet in diesem Arbeitsbereich und sieht alle Rezepte + Wochenpläne.
          </div>
          <div
            style={{
              display: "flex",
              alignItems: "center",
              gap: "0.5rem",
              background: "#fff",
              border: "1.5px solid #e4dccc",
              borderRadius: "8px",
              padding: "0.55rem 0.75rem",
              marginBottom: "0.5rem",
            }}
          >
            <code
              style={{
                flex: 1,
                fontFamily: "'Fraunces', serif",
                fontSize: "1.15rem",
                fontWeight: 600,
                letterSpacing: "0.05em",
                color: "#3d4a2a",
                overflowX: "auto",
                whiteSpace: "nowrap",
              }}
            >
              {workspace.inviteCode}
            </code>
            <button
              className="btn-secondary"
              onClick={() => copyInvite(workspace.inviteCode)}
              style={{ padding: "0.4rem 0.75rem", fontSize: "0.85rem" }}
            >
              {copied ? <Check size={14} /> : <Copy size={14} />}
              {copied ? "Kopiert" : "Kopieren"}
            </button>
          </div>
          <button
            className="btn-ghost"
            onClick={() => copyInvite(inviteLink)}
            style={{ fontSize: "0.82rem", padding: "0.3rem 0" }}
            title="Beitritts-Link kopieren"
          >
            <Link2 size={13} /> Als Link kopieren
          </button>

          {isOwner && (
            <div style={{ marginTop: "0.6rem", fontSize: "0.82rem" }}>
              {regenConfirm ? (
                <div
                  style={{
                    background: "rgba(168, 66, 42, 0.08)",
                    border: "1px solid rgba(168, 66, 42, 0.25)",
                    borderRadius: "6px",
                    padding: "0.55rem 0.75rem",
                    color: "#a8422a",
                    display: "flex",
                    alignItems: "center",
                    gap: "0.5rem",
                    flexWrap: "wrap",
                  }}
                >
                  <span>Neuen Code erzeugen? Der alte wird ungültig.</span>
                  <button
                    className="btn-ghost"
                    onClick={async () => {
                      await onRegenerateInvite();
                      setRegenConfirm(false);
                    }}
                    style={{ color: "#a8422a", fontWeight: 600 }}
                  >
                    Ja, neu erzeugen
                  </button>
                  <button className="btn-ghost" onClick={() => setRegenConfirm(false)}>
                    Abbrechen
                  </button>
                </div>
              ) : (
                <button
                  className="btn-ghost"
                  onClick={() => setRegenConfirm(true)}
                  style={{ color: "#a8422a", fontSize: "0.82rem", padding: "0.3rem 0" }}
                  title="Alten Code ungültig machen und neu erzeugen"
                >
                  <RefreshCw size={13} /> Neuen Code erzeugen
                </button>
              )}
            </div>
          )}
        </div>

        {/* Mitglieder */}
        <div style={{ marginBottom: "1.4rem" }}>
          <h3
            className="font-display"
            style={{ fontSize: "1.05rem", fontWeight: 600, margin: "0 0 0.6rem" }}
          >
            Mitglieder
          </h3>
          <div style={{ display: "flex", flexDirection: "column", gap: "0.4rem" }}>
            {members.length === 0 ? (
              <div style={{ color: "#8a8275", fontSize: "0.88rem" }}>
                Noch keine Mitglieder geladen.
              </div>
            ) : (
              members.map((m) => (
                <div
                  key={m.userId}
                  style={{
                    display: "flex",
                    alignItems: "center",
                    gap: "0.6rem",
                    padding: "0.55rem 0.7rem",
                    background: "#fff",
                    border: "1px solid #e4dccc",
                    borderRadius: "8px",
                  }}
                >
                  <Avatar name={m.email} size={28} />
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div
                      style={{
                        fontSize: "0.9rem",
                        fontWeight: 500,
                        whiteSpace: "nowrap",
                        overflow: "hidden",
                        textOverflow: "ellipsis",
                      }}
                    >
                      {m.email}
                      {m.userId === currentUserId && (
                        <span style={{ color: "#8a8275", fontWeight: 400 }}> · du</span>
                      )}
                    </div>
                    <div style={{ fontSize: "0.76rem", color: "#8a8275" }}>
                      {m.role === "owner" ? "Besitzer:in" : "Mitglied"} · beigetreten{" "}
                      {new Date(m.joinedAt).toLocaleDateString("de-DE", {
                        day: "2-digit",
                        month: "2-digit",
                        year: "numeric",
                      })}
                    </div>
                  </div>
                </div>
              ))
            )}
          </div>
        </div>

        {/* Hinweise */}
        <div
          style={{
            fontSize: "0.82rem",
            color: "#6b6358",
            paddingTop: "0.8rem",
            borderTop: "1px dashed #e4dccc",
            lineHeight: 1.5,
          }}
        >
          <div style={{ marginBottom: "0.25rem" }}>
            Änderungen werden zwischen allen Mitgliedern automatisch synchronisiert.
          </div>
          <div>
            Ein Rezept öffentlich teilen? → im Rezept den „Öffentlich"-Schalter umlegen.
            Rezepte anderer entdecken? → im Rezepte-Tab auf „Entdecken" wechseln.
          </div>
        </div>

        <div
          style={{
            marginTop: "1.2rem",
            paddingTop: "1rem",
            borderTop: "1px dashed #e4dccc",
          }}
        >
          <div
            style={{
              fontSize: "0.78rem",
              letterSpacing: "0.06em",
              color: "#8a8275",
              textTransform: "uppercase",
              fontWeight: 600,
              marginBottom: "0.5rem",
            }}
          >
            Datenpflege
          </div>
          <div
            style={{
              display: "flex",
              alignItems: "center",
              justifyContent: "space-between",
              gap: "0.75rem",
              flexWrap: "wrap",
            }}
          >
            <div style={{ fontSize: "0.85rem", color: "#6b6358", flex: "1 1 240px" }}>
              Entfernt einmalig Sektions-Präfixe wie <code>[Außerdem:]</code> oder{" "}
              <code>[Für das Pesto:]</code> aus importierten Zutatennamen.
            </div>
            <button
              className="btn-ghost"
              onClick={onCleanupIngredients}
              style={{ flexShrink: 0 }}
              title="Räumt Zutatennamen in allen Rezepten auf"
            >
              <Sparkles size={14} /> Zutatennamen aufräumen
            </button>
          </div>
        </div>

        <div
          style={{
            display: "flex",
            justifyContent: "space-between",
            alignItems: "center",
            marginTop: "1.2rem",
            gap: "0.5rem",
            flexWrap: "wrap",
          }}
        >
          <button
            className="btn-ghost"
            onClick={onLeave}
            style={{ color: "#a8422a" }}
            title={members.length <= 1 ? "Als einziges Mitglied = Bereich löschen" : "Diesen Arbeitsbereich verlassen"}
          >
            <LogOut size={14} />
            {members.length <= 1 ? "Bereich löschen" : "Verlassen"}
          </button>
          <button className="btn-primary" onClick={onClose}>
            Verstanden
          </button>
        </div>
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Onboarding-Screen: zeigt sich nach Signup / beim Fehlen eines Workspaces
// ────────────────────────────────────────────────────────────────────
function OnboardingScreen({ userName, onCreate, onJoin, onLogout }) {
  // Beitritts-Link vorfüllen, falls in URL: #join=CODE
  const initialCode = (() => {
    try {
      const h = window.location.hash || "";
      const m = h.match(/join=([^&]+)/);
      return m ? decodeURIComponent(m[1]) : "";
    } catch {
      return "";
    }
  })();

  const [mode, setMode] = useState(initialCode ? "join" : "create");
  const [workspaceName, setWorkspaceName] = useState("Zuhause");
  const [inviteCode, setInviteCode] = useState(initialCode);
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState("");

  const submit = async (e) => {
    e.preventDefault();
    setError("");
    setBusy(true);
    try {
      if (mode === "create") {
        const clean = workspaceName.trim() || "Zuhause";
        await onCreate(clean);
      } else {
        const clean = inviteCode.trim().toUpperCase();
        if (!clean) throw new Error("Bitte einen Einladungs-Code eingeben.");
        await onJoin(clean);
      }
    } catch (e2) {
      console.error(e2);
      setError(e2?.message || "Aktion fehlgeschlagen");
    } finally {
      setBusy(false);
    }
  };

  return (
    <div
      style={{
        minHeight: "100vh",
        background: "#f5efe4",
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        padding: "2rem 1rem",
      }}
    >
      <style>{`
        @import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,500;0,9..144,600;0,9..144,700;1,9..144,400&family=Instrument+Sans:wght@400;500;600&display=swap');
        .font-display { font-family: 'Fraunces', Georgia, serif; font-optical-sizing: auto; }
        body, input, button, select, textarea { font-family: 'Instrument Sans', system-ui, sans-serif; }
      `}</style>
      <div style={{ width: "100%", maxWidth: "28rem" }}>
        <div style={{ textAlign: "center", marginBottom: "1.5rem" }}>
          <div style={{ display: "inline-block", marginBottom: "0.9rem" }}>
            <LoeffelListeLogo size={56} />
          </div>
          <h1
            className="font-display"
            style={{ fontSize: "1.9rem", fontWeight: 600, margin: 0, letterSpacing: "-0.02em" }}
          >
            Willkommen{userName ? `, ${userName}` : ""}
          </h1>
          <p
            style={{
              color: "#6b6358",
              marginTop: "0.4rem",
              fontFamily: "'Fraunces', serif",
              fontStyle: "italic",
            }}
          >
            Richte deinen Arbeitsbereich ein
          </p>
        </div>

        <form
          onSubmit={submit}
          style={{
            background: "#fdfaf3",
            border: "1.5px solid #e4dccc",
            borderRadius: "12px",
            padding: "1.6rem",
            boxShadow: "0 4px 20px rgba(0,0,0,0.04)",
          }}
        >
          <div
            style={{
              display: "flex",
              gap: "0.25rem",
              padding: "0.25rem",
              background: "rgba(61, 74, 42, 0.06)",
              borderRadius: "8px",
              marginBottom: "1.3rem",
            }}
          >
            {[
              { key: "create", label: "Neu anlegen" },
              { key: "join", label: "Beitreten" },
            ].map((m) => (
              <button
                key={m.key}
                type="button"
                onClick={() => {
                  setMode(m.key);
                  setError("");
                }}
                style={{
                  flex: 1,
                  padding: "0.55rem",
                  background: mode === m.key ? "#fdfaf3" : "transparent",
                  color: mode === m.key ? "#3d4a2a" : "#6b6358",
                  fontWeight: mode === m.key ? 600 : 500,
                  border: "none",
                  borderRadius: "6px",
                  cursor: "pointer",
                  fontSize: "0.92rem",
                  boxShadow: mode === m.key ? "0 1px 3px rgba(0,0,0,0.06)" : "none",
                }}
              >
                {m.label}
              </button>
            ))}
          </div>

          {mode === "create" ? (
            <>
              <div style={{ color: "#4a443b", fontSize: "0.88rem", marginBottom: "1rem", lineHeight: 1.5 }}>
                Lege deinen eigenen Arbeitsbereich an. Rezepte und Wochenpläne
                darin siehst nur du – bis du andere per Einladungs-Code dazu holst.
              </div>
              <div>
                <label style={labelStyle}>Name des Arbeitsbereichs</label>
                <input
                  className="input-base"
                  style={{ width: "100%" }}
                  value={workspaceName}
                  onChange={(e) => setWorkspaceName(e.target.value)}
                  placeholder="z.B. Zuhause, WG, Familie Müller"
                  maxLength={40}
                  autoFocus
                />
              </div>
            </>
          ) : (
            <>
              <div style={{ color: "#4a443b", fontSize: "0.88rem", marginBottom: "1rem", lineHeight: 1.5 }}>
                Trete dem Arbeitsbereich einer anderen Person bei. Du brauchst
                ihren Einladungs-Code (sieht aus wie <code style={{ background: "rgba(61,74,42,0.08)", padding: "0 0.3rem", borderRadius: "3px" }}>MEAL-XXXX-YYYY</code>).
              </div>
              <div>
                <label style={labelStyle}>Einladungs-Code</label>
                <input
                  className="input-base"
                  style={{
                    width: "100%",
                    fontFamily: "'Fraunces', serif",
                    fontSize: "1.05rem",
                    letterSpacing: "0.05em",
                    textTransform: "uppercase",
                  }}
                  value={inviteCode}
                  onChange={(e) => setInviteCode(e.target.value)}
                  placeholder="MEAL-XXXX-YYYY"
                  autoFocus
                />
              </div>
            </>
          )}

          {error && (
            <div
              style={{
                marginTop: "1rem",
                padding: "0.65rem 0.85rem",
                background: "rgba(168, 66, 42, 0.08)",
                border: "1px solid rgba(168, 66, 42, 0.25)",
                borderRadius: "6px",
                color: "#a8422a",
                fontSize: "0.87rem",
                display: "flex",
                alignItems: "flex-start",
                gap: "0.5rem",
              }}
            >
              <AlertCircle size={15} style={{ flexShrink: 0, marginTop: "0.1rem" }} />
              <span>{error}</span>
            </div>
          )}

          <button
            type="submit"
            disabled={busy}
            style={{
              marginTop: "1.4rem",
              width: "100%",
              padding: "0.8rem",
              background: "#3d4a2a",
              color: "#f5efe4",
              border: "none",
              borderRadius: "8px",
              fontWeight: 600,
              fontSize: "0.95rem",
              cursor: busy ? "wait" : "pointer",
              opacity: busy ? 0.6 : 1,
              display: "inline-flex",
              alignItems: "center",
              justifyContent: "center",
              gap: "0.5rem",
            }}
          >
            {mode === "create" ? <Plus size={16} /> : <UserPlus size={16} />}
            {busy
              ? "Einen Moment…"
              : mode === "create"
              ? "Arbeitsbereich anlegen"
              : "Beitreten"}
          </button>

          <div
            style={{
              marginTop: "1rem",
              fontSize: "0.82rem",
              color: "#8a8275",
              textAlign: "center",
            }}
          >
            Falsches Konto?{" "}
            <button
              type="button"
              onClick={onLogout}
              style={{
                background: "none",
                border: "none",
                color: "#a8422a",
                cursor: "pointer",
                fontWeight: 600,
                padding: 0,
                fontFamily: "inherit",
                fontSize: "0.82rem",
              }}
            >
              Abmelden
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// DiscoverView: öffentliche Rezepte aller Nutzer
// ────────────────────────────────────────────────────────────────────
function DiscoverView({ publicRecipes, loadedAt, onRefresh, onOpen, viewingId, onBack, onStartPlan }) {
  const [search, setSearch] = useState("");
  const [categoryFilter, setCategoryFilter] = useState("all");

  useEffect(() => {
    if (!loadedAt) onRefresh();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  if (viewingId) {
    const recipe = publicRecipes.find((r) => r.id === viewingId);
    if (!recipe) {
      return (
        <div className="fade-in">
          <button className="btn-ghost" onClick={onBack}>
            <ChevronLeft size={16} /> Zurück
          </button>
          <div style={{ padding: "2rem", color: "#8a8275" }}>
            Rezept nicht mehr verfügbar. Der Ersteller hat es möglicherweise
            zurückgezogen.
          </div>
        </div>
      );
    }
    return (
      <PublicRecipeDetail
        recipe={recipe}
        onBack={onBack}
        onPlan={() => onStartPlan(recipe)}
      />
    );
  }

  const filtered = publicRecipes
    .filter((r) => r.name.toLowerCase().includes(search.toLowerCase()))
    .filter(
      (r) => categoryFilter === "all" || (r.recipeCategory || "main") === categoryFilter
    )
    .sort((a, b) => a.name.localeCompare(b.name, "de"));

  const usedCategories = RECIPE_CATEGORIES.map((c) => ({
    ...c,
    count: publicRecipes.filter((r) => (r.recipeCategory || "main") === c.id).length,
  })).filter((c) => c.count > 0);

  return (
    <div className="fade-in">
      <div
        style={{
          display: "flex",
          alignItems: "center",
          gap: "0.6rem",
          marginBottom: "0.85rem",
          flexWrap: "wrap",
        }}
      >
        <div style={{ position: "relative", flex: "1 1 16rem" }}>
          <Search
            size={16}
            style={{
              position: "absolute",
              left: "0.75rem",
              top: "50%",
              transform: "translateY(-50%)",
              color: "#8a8275",
            }}
          />
          <input
            className="input-base"
            placeholder="Community-Rezepte durchsuchen…"
            value={search}
            onChange={(e) => setSearch(e.target.value)}
            style={{ paddingLeft: "2.25rem", width: "100%" }}
          />
        </div>
        <button className="btn-secondary" onClick={onRefresh} title="Neu laden">
          <RefreshCw size={14} /> Neu laden
        </button>
      </div>

      <div
        style={{
          background: "rgba(74, 107, 138, 0.08)",
          border: "1px solid rgba(74, 107, 138, 0.2)",
          borderRadius: "8px",
          padding: "0.7rem 0.9rem",
          marginBottom: "1rem",
          fontSize: "0.88rem",
          color: "#3d4a2a",
          display: "flex",
          alignItems: "flex-start",
          gap: "0.5rem",
        }}
      >
        <Info size={15} style={{ color: "#4a6b8a", flexShrink: 0, marginTop: "0.15rem" }} />
        <div style={{ lineHeight: 1.5 }}>
          Rezepte anderer App-Nutzer:innen, die öffentlich geteilt wurden.
          Du kannst sie ansehen und in deinen Wochenplan einplanen – Zutaten und
          Name werden beim Einplanen mit deinem Plan gespeichert.
        </div>
      </div>

      {usedCategories.length > 0 && (
        <div
          style={{
            display: "flex",
            gap: "0.4rem",
            flexWrap: "wrap",
            marginBottom: "1.25rem",
          }}
        >
          <CategoryPill
            label="Alle"
            active={categoryFilter === "all"}
            count={publicRecipes.length}
            onClick={() => setCategoryFilter("all")}
          />
          {usedCategories.map((c) => (
            <CategoryPill
              key={c.id}
              label={c.label}
              icon={c.icon}
              color={c.color}
              count={c.count}
              active={categoryFilter === c.id}
              onClick={() => setCategoryFilter(c.id)}
            />
          ))}
        </div>
      )}

      {filtered.length === 0 ? (
        <div
          className="card"
          style={{
            padding: "2.5rem 1.5rem",
            textAlign: "center",
            borderStyle: "dashed",
          }}
        >
          <Sparkles size={28} style={{ color: "#4a6b8a", margin: "0 auto 0.75rem" }} />
          <div style={{ fontWeight: 600, marginBottom: "0.25rem" }}>
            {search || categoryFilter !== "all" ? "Keine Treffer" : "Noch keine öffentlichen Rezepte"}
          </div>
          <div style={{ color: "#6b6358" }}>
            {search || categoryFilter !== "all"
              ? "Passe die Suche oder den Filter an."
              : "Sobald jemand ein Rezept öffentlich teilt, erscheint es hier."}
          </div>
        </div>
      ) : (
        <div
          style={{
            display: "grid",
            gap: "0.75rem",
            gridTemplateColumns: "repeat(auto-fill, minmax(18rem, 1fr))",
          }}
        >
          {filtered.map((r) => (
            <PublicRecipeCard key={r.id} recipe={r} onOpen={onOpen} />
          ))}
        </div>
      )}
    </div>
  );
}

function PublicRecipeCard({ recipe: r, onOpen }) {
  const cat = getRecipeCategory(r.recipeCategory);
  return (
    <div
      className="card card-clickable"
      onClick={() => onOpen(r.id)}
      style={{
        padding: "1rem 1.1rem",
        display: "flex",
        flexDirection: "column",
        borderLeft: `3px solid ${cat.color}`,
      }}
    >
      <div
        className="pill"
        style={{
          background: `${cat.color}15`,
          color: cat.color,
          marginBottom: "0.4rem",
          alignSelf: "flex-start",
        }}
      >
        <span>{cat.icon}</span> {cat.label}
      </div>
      <h3
        className="font-display"
        style={{
          fontSize: "1.15rem",
          fontWeight: 600,
          lineHeight: 1.25,
          margin: "0 0 0.5rem 0",
        }}
      >
        {r.name}
      </h3>
      <div
        style={{
          color: "#6b6358",
          fontSize: "0.8rem",
          display: "flex",
          alignItems: "center",
          gap: "0.6rem",
          marginBottom: "0.5rem",
          flexWrap: "wrap",
        }}
      >
        <span style={{ display: "inline-flex", alignItems: "center", gap: "0.25rem" }}>
          <Users size={13} /> {r.basePortions} P.
        </span>
        <span>·</span>
        <span>{r.ingredients.length} Zutaten</span>
        {r.createdBy && (
          <span style={{ marginLeft: "auto", color: "#4a6b8a" }}>
            von {r.createdBy}
          </span>
        )}
      </div>
      <div
        style={{
          marginTop: "auto",
          paddingTop: "0.6rem",
          borderTop: "1px dashed #e4dccc",
          display: "flex",
          alignItems: "center",
          justifyContent: "flex-end",
          color: "#4a6b8a",
          fontSize: "0.8rem",
        }}
      >
        <span style={{ marginRight: "auto" }}>Öffnen & einplanen</span>
        <ArrowRight size={14} />
      </div>
    </div>
  );
}

function PublicRecipeDetail({ recipe, onBack, onPlan }) {
  const cat = getRecipeCategory(recipe.recipeCategory);
  return (
    <div className="fade-in">
      <div
        style={{
          display: "flex",
          alignItems: "center",
          justifyContent: "space-between",
          marginBottom: "1rem",
          flexWrap: "wrap",
          gap: "0.5rem",
        }}
      >
        <button className="btn-ghost" onClick={onBack}>
          <ChevronLeft size={16} /> Zurück zu Entdecken
        </button>
        <button className="btn-primary" onClick={onPlan}>
          <Calendar size={15} /> In Wochenplan einplanen
        </button>
      </div>

      <div
        className="card"
        style={{
          padding: "1.5rem",
          marginBottom: "1rem",
          borderLeft: `4px solid ${cat.color}`,
        }}
      >
        <div
          className="pill"
          style={{
            background: `${cat.color}15`,
            color: cat.color,
            marginBottom: "0.75rem",
          }}
        >
          <span>{cat.icon}</span> {cat.label}
        </div>
        <h1
          className="font-display"
          style={{
            fontSize: "2rem",
            fontWeight: 600,
            letterSpacing: "-0.01em",
            margin: "0 0 0.5rem 0",
            lineHeight: 1.1,
          }}
        >
          {recipe.name}
        </h1>
        <div
          style={{
            display: "flex",
            gap: "1rem",
            flexWrap: "wrap",
            color: "#6b6358",
            fontSize: "0.9rem",
            marginTop: "0.75rem",
          }}
        >
          <div style={{ display: "flex", alignItems: "center", gap: "0.3rem" }}>
            <Users size={14} /> {recipe.basePortions} Portionen (Basis)
          </div>
          <div style={{ display: "flex", alignItems: "center", gap: "0.3rem" }}>
            <Utensils size={14} /> {recipe.ingredients.length} Zutaten
          </div>
          {recipe.createdBy && (
            <div style={{ display: "flex", alignItems: "center", gap: "0.3rem", color: "#4a6b8a" }}>
              geteilt von {recipe.createdBy}
            </div>
          )}
        </div>
        {recipe.notes && (
          <div
            style={{
              marginTop: "1rem",
              padding: "0.85rem 1rem",
              background: "rgba(192, 106, 67, 0.06)",
              border: "1px solid rgba(192, 106, 67, 0.2)",
              borderRadius: "8px",
              fontFamily: "'Fraunces', serif",
              fontStyle: "italic",
              color: "#4a443b",
              fontSize: "0.95rem",
            }}
          >
            {'„'}{recipe.notes}{'"'}
          </div>
        )}
      </div>

      <div className="card" style={{ padding: "1.25rem" }}>
        <h2
          className="font-display"
          style={{
            fontSize: "1.2rem",
            fontWeight: 600,
            margin: "0 0 0.85rem 0",
          }}
        >
          Zutaten
        </h2>
        <ul style={{ margin: 0, paddingLeft: "1.25rem", color: "#2a2620" }}>
          {recipe.ingredients.map((ing, idx) => (
            <li key={idx} style={{ marginBottom: "0.3rem" }}>
              {ing.amount ? `${prettyNumber(ing.amount)} ${ing.unit} ` : ""}
              <strong>{ing.name}</strong>
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// PlanPublicRecipeModal – wähle Woche/Tag/Portionen
// ────────────────────────────────────────────────────────────────────
function PlanPublicRecipeModal({ recipe, currentMonday, plans, onCancel, onConfirm }) {
  const [weekMonday, setWeekMonday] = useState(currentMonday);
  const [dayKey, setDayKey] = useState(() => {
    // Vorschlag: erster freier Tag der aktuellen Woche
    const weekKey = toISODate(currentMonday);
    const plan = plans[weekKey];
    for (const d of DAYS) {
      if (!plan?.days?.[d.key]?.recipeId) return d.key;
    }
    return "mon";
  });
  const [portions, setPortions] = useState(recipe.basePortions || 2);

  const weekKey = toISODate(weekMonday);
  const plan = plans[weekKey] || { days: {} };
  const currentDay = plan.days?.[dayKey];
  const willOverwrite = !!currentDay?.recipeId;

  const shiftWeek = (n) => setWeekMonday(addDays(weekMonday, n * 7));

  return (
    <div
      style={{
        position: "fixed",
        inset: 0,
        background: "rgba(42, 38, 32, 0.55)",
        zIndex: 60,
        display: "flex",
        alignItems: "flex-start",
        justifyContent: "center",
        padding: "2rem 1rem",
        overflowY: "auto",
      }}
      onClick={onCancel}
    >
      <div
        className="card scale-in"
        style={{
          maxWidth: "28rem",
          width: "100%",
          padding: "1.5rem",
          background: "#fdfaf3",
          boxShadow: "0 25px 60px rgba(0,0,0,0.25)",
        }}
        onClick={(e) => e.stopPropagation()}
      >
        <div
          style={{
            display: "flex",
            justifyContent: "space-between",
            alignItems: "center",
            marginBottom: "1rem",
          }}
        >
          <h2
            className="font-display"
            style={{ fontSize: "1.3rem", fontWeight: 600, margin: 0 }}
          >
            In Wochenplan einplanen
          </h2>
          <button className="btn-ghost" onClick={onCancel}>
            <X size={18} />
          </button>
        </div>

        <div
          style={{
            background: "rgba(74, 107, 138, 0.08)",
            border: "1px solid rgba(74, 107, 138, 0.2)",
            borderRadius: "8px",
            padding: "0.7rem 0.9rem",
            marginBottom: "1rem",
            fontSize: "0.88rem",
          }}
        >
          <div style={{ fontWeight: 600, marginBottom: "0.2rem" }}>{recipe.name}</div>
          <div style={{ color: "#6b6358", fontSize: "0.82rem" }}>
            {recipe.ingredients.length} Zutaten · Basis {recipe.basePortions} Portionen
            {recipe.createdBy ? ` · geteilt von ${recipe.createdBy}` : ""}
          </div>
        </div>

        {/* Wochen-Navigation */}
        <div>
          <label style={labelStyle}>Woche</label>
          <div
            style={{
              display: "flex",
              alignItems: "center",
              gap: "0.4rem",
              marginBottom: "1rem",
            }}
          >
            <button className="btn-ghost" onClick={() => shiftWeek(-1)} title="Vorige Woche">
              <ChevronLeft size={16} />
            </button>
            <div
              style={{
                flex: 1,
                textAlign: "center",
                fontSize: "0.9rem",
                fontWeight: 500,
                color: "#2a2620",
              }}
            >
              KW {getWeekNumber(weekMonday)} · {formatWeekRange(weekMonday)}
            </div>
            <button className="btn-ghost" onClick={() => shiftWeek(1)} title="Nächste Woche">
              <ChevronRight size={16} />
            </button>
          </div>
        </div>

        {/* Tag-Auswahl */}
        <div style={{ marginBottom: "1rem" }}>
          <label style={labelStyle}>Tag</label>
          <div
            style={{
              display: "grid",
              gridTemplateColumns: "repeat(7, 1fr)",
              gap: "0.3rem",
            }}
          >
            {DAYS.map((d) => {
              const occupied = !!plan.days?.[d.key]?.recipeId;
              const isSelected = dayKey === d.key;
              return (
                <button
                  key={d.key}
                  onClick={() => setDayKey(d.key)}
                  style={{
                    padding: "0.5rem 0.2rem",
                    background: isSelected ? "#3d4a2a" : occupied ? "rgba(192, 106, 67, 0.15)" : "#fff",
                    color: isSelected ? "#f5efe4" : "#2a2620",
                    border: `1.5px solid ${isSelected ? "#3d4a2a" : occupied ? "rgba(192, 106, 67, 0.4)" : "#e4dccc"}`,
                    borderRadius: "6px",
                    cursor: "pointer",
                    fontSize: "0.82rem",
                    fontWeight: isSelected ? 600 : 500,
                  }}
                  title={occupied ? "Tag ist belegt – Einplanen überschreibt" : ""}
                >
                  {d.short}
                </button>
              );
            })}
          </div>
          {willOverwrite && (
            <div
              style={{
                marginTop: "0.5rem",
                fontSize: "0.8rem",
                color: "#a8422a",
                display: "flex",
                alignItems: "center",
                gap: "0.3rem",
              }}
            >
              <AlertCircle size={13} /> Der Tag ist belegt – Einplanen überschreibt den vorhandenen Eintrag.
            </div>
          )}
        </div>

        {/* Portionen */}
        <div style={{ marginBottom: "1.25rem" }}>
          <label style={labelStyle}>Portionen</label>
          <input
            className="input-base"
            type="number"
            min="1"
            max="99"
            value={portions}
            onChange={(e) => setPortions(Math.max(1, Math.min(99, Number(e.target.value) || 1)))}
            style={{ width: "6rem" }}
          />
        </div>

        <div style={{ display: "flex", justifyContent: "flex-end", gap: "0.5rem" }}>
          <button className="btn-ghost" onClick={onCancel}>
            Abbrechen
          </button>
          <button
            className="btn-primary"
            onClick={() => onConfirm({ dayKey, portions, weekKey })}
          >
            <Check size={15} /> Einplanen
          </button>
        </div>
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Name Modal
// ────────────────────────────────────────────────────────────────────
function NameModal({ currentName, onSave, onCancel }) {
  const [draft, setDraft] = useState(currentName || "");
  const isFirstTime = !currentName;

  const handleSubmit = () => {
    if (draft.trim()) onSave(draft);
  };

  return (
    <div
      style={{
        position: "fixed",
        inset: 0,
        background: "rgba(42, 38, 32, 0.6)",
        zIndex: 60,
        display: "flex",
        alignItems: "flex-start",
        justifyContent: "center",
        padding: "2rem 1rem",
        overflowY: "auto",
      }}
      onClick={() => onCancel && onCancel()}
    >
      <div
        className="card scale-in"
        style={{
          maxWidth: "26rem",
          width: "100%",
          padding: "1.5rem",
          background: "#fdfaf3",
          boxShadow: "0 25px 60px rgba(0,0,0,0.25)",
        }}
        onClick={(e) => e.stopPropagation()}
      >
        <div
          style={{
            display: "flex",
            justifyContent: "space-between",
            alignItems: "center",
            marginBottom: "0.6rem",
          }}
        >
          <h2
            className="font-display"
            style={{
              fontSize: "1.4rem",
              fontWeight: 600,
              margin: 0,
              display: "flex",
              alignItems: "center",
              gap: "0.5rem",
            }}
          >
            {isFirstTime ? "Willkommen 👋" : "Name ändern"}
          </h2>
          {onCancel && (
            <button className="btn-ghost" onClick={onCancel}>
              <X size={18} />
            </button>
          )}
        </div>

        <p style={{ color: "#6b6358", fontSize: "0.92rem", marginTop: 0, marginBottom: "1rem" }}>
          {isFirstTime
            ? "Wie sollen dich andere sehen? Dein Name erscheint als Kürzel neben Rezepten und Tagen, die du bearbeitest."
            : "Dein Anzeigename für neue Einträge. Bestehende Einträge bleiben unverändert."}
        </p>

        <label style={labelStyle}>Dein Name</label>
        <div style={{ display: "flex", gap: "0.6rem", alignItems: "center" }}>
          <Avatar name={draft || "?"} size={42} />
          <input
            className="input-base"
            style={{ flex: 1 }}
            value={draft}
            maxLength={24}
            placeholder="z.B. Anna oder A.B."
            onChange={(e) => setDraft(e.target.value)}
            onKeyDown={(e) => {
              if (e.key === "Enter") handleSubmit();
            }}
            autoFocus
          />
        </div>
        <div
          style={{
            fontSize: "0.8rem",
            color: "#8a8275",
            marginTop: "0.5rem",
            display: "flex",
            alignItems: "center",
            gap: "0.35rem",
          }}
        >
          <Info size={12} />
          Initialen werden automatisch erzeugt · Farbe wird zugewiesen
        </div>

        <div
          style={{
            display: "flex",
            justifyContent: "flex-end",
            gap: "0.6rem",
            marginTop: "1.3rem",
            paddingTop: "1rem",
            borderTop: "1px solid #e4dccc",
          }}
        >
          {onCancel && (
            <button className="btn-secondary" onClick={onCancel}>
              Abbrechen
            </button>
          )}
          <button className="btn-primary" onClick={handleSubmit} disabled={!draft.trim()}>
            <Check size={16} /> {isFirstTime ? "Los geht's" : "Speichern"}
          </button>
        </div>
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Confirm Dialog
// ────────────────────────────────────────────────────────────────────
function ConfirmDialog({ state }) {
  return (
    <div
      style={{
        position: "fixed",
        inset: 0,
        background: "rgba(42, 38, 32, 0.6)",
        zIndex: 70,
        display: "flex",
        alignItems: "flex-start",
        justifyContent: "center",
        padding: "2rem 1rem",
      }}
      onClick={() => state.onResolve(false)}
    >
      <div
        className="card scale-in"
        style={{
          maxWidth: "26rem",
          width: "100%",
          padding: "1.4rem 1.5rem",
          background: "#fdfaf3",
          boxShadow: "0 25px 60px rgba(0,0,0,0.25)",
          marginTop: "15vh",
        }}
        onClick={(e) => e.stopPropagation()}
      >
        <div
          style={{
            display: "flex",
            gap: "0.75rem",
            alignItems: "flex-start",
            marginBottom: "0.75rem",
          }}
        >
          <div
            style={{
              background: state.danger ? "rgba(168, 66, 42, 0.12)" : "rgba(61, 74, 42, 0.1)",
              color: state.danger ? "#a8422a" : "#3d4a2a",
              borderRadius: "50%",
              width: "36px",
              height: "36px",
              display: "inline-flex",
              alignItems: "center",
              justifyContent: "center",
              flexShrink: 0,
            }}
          >
            <AlertCircle size={18} />
          </div>
          <div>
            <h3
              className="font-display"
              style={{ fontSize: "1.2rem", fontWeight: 600, margin: "0 0 0.25rem 0" }}
            >
              {state.title}
            </h3>
            <p style={{ color: "#4a443b", fontSize: "0.93rem", margin: 0, lineHeight: 1.5 }}>
              {state.message}
            </p>
          </div>
        </div>

        <div
          style={{
            display: "flex",
            justifyContent: "flex-end",
            gap: "0.5rem",
            marginTop: "1.25rem",
            paddingTop: "0.9rem",
            borderTop: "1px solid #e4dccc",
          }}
        >
          <button className="btn-secondary" onClick={() => state.onResolve(false)} autoFocus>
            Abbrechen
          </button>
          <button
            onClick={() => state.onResolve(true)}
            style={{
              background: state.danger ? "#a8422a" : "#3d4a2a",
              color: "#fdfaf3",
              padding: "0.6rem 1.1rem",
              borderRadius: "6px",
              border: "none",
              cursor: "pointer",
              fontWeight: 500,
              display: "inline-flex",
              alignItems: "center",
              gap: "0.4rem",
              transition: "background 0.15s",
            }}
            onMouseEnter={(e) => {
              e.currentTarget.style.background = state.danger ? "#8a3522" : "#2d3820";
            }}
            onMouseLeave={(e) => {
              e.currentTarget.style.background = state.danger ? "#a8422a" : "#3d4a2a";
            }}
          >
            {state.danger && <Trash2 size={15} />}
            {!state.danger && <Check size={15} />}
            {state.confirmLabel}
          </button>
        </div>
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Shopping View
// ────────────────────────────────────────────────────────────────────
function ShoppingView({
  plans,
  recipes,
  groups,
  categories,
  ingredientCategories,
  onAssignCategory,
  weekMonday,
  onUnconfirm,
  onAddPurchase,
  shoppingChecks,
  purchasedItems,
  onToggleCheck,
  onMarkPurchased,
  onRestorePurchased,
  onClearChecks,
  showToast,
}) {
  // Welche Zutat hat aktuell ein offenes Kategorie-Edit-Feld?
  const [editingCategory, setEditingCategory] = useState(null);

  // Modal-State für "Einkauf abschließen"
  const [showCompleteDialog, setShowCompleteDialog] = useState(false);

  // Klappzustand des "Bereits gekauft"-Bereichs
  const [showPurchased, setShowPurchased] = useState(false);

  // Stabiler Key pro Zutat: unabhängig von Position oder Gruppen-Index.
  // So bleiben Häkchen erhalten, auch wenn Wochen hinzugefügt/entfernt
  // werden oder sich die Sortierung ändert.
  const itemKey = (i) => `${i.name.toLowerCase()}|${(i.unit || "").toLowerCase()}`;

  // Liste aller Wochen mit geplanten Rezepten (für die Wochenleiste)
  const weekBar = useMemo(() => {
    if (!plans) return [];
    return Object.entries(plans)
      .filter(([, p]) => Object.values(p?.days || {}).some((d) => d?.recipeId))
      .map(([k, p]) => {
        const [y, m, d] = k.split("-").map(Number);
        const monday = new Date(y, m - 1, d);
        const recipeCount = Object.values(p.days || {}).filter((x) => x?.recipeId).length;
        return {
          key: k,
          monday,
          plan: p,
          weekNr: getWeekNumber(monday),
          confirmed: !!p.confirmedAt,
          recipeCount,
        };
      })
      .sort((a, b) => a.key.localeCompare(b.key));
  }, [plans]);

  // Default: aktuelle Woche aktiv. Wird beim Wechsel von weekMonday neu gesetzt.
  const initialActive = useMemo(() => {
    const k = toISODate(weekMonday);
    return new Set([k]);
  }, [weekMonday]);

  const [activeKeys, setActiveKeys] = useState(initialActive);

  // Sync wenn weekMonday sich ändert (z.B. aus Wochenplan-Tab kommend
  // oder nach frischer Bestätigung).
  useEffect(() => {
    setActiveKeys(new Set([toISODate(weekMonday)]));
  }, [weekMonday]);

  // Falls die aktuelle Woche keine Rezepte hat, aber andere Wochen welche
  // haben, fall-back auf die nächste verfügbare Woche.
  useEffect(() => {
    if (activeKeys.size === 0 && weekBar.length > 0) {
      const todayKey = toISODate(getMonday());
      const fallback = weekBar.find((w) => w.key >= todayKey) || weekBar[weekBar.length - 1];
      setActiveKeys(new Set([fallback.key]));
    }
  }, [activeKeys, weekBar]);

  // Sortierte Liste der aktiven Wochen-Schlüssel (chronologisch)
  const selectedKeys = useMemo(
    () => [...activeKeys].sort((a, b) => a.localeCompare(b)),
    [activeKeys]
  );

  const selectedPlans = useMemo(
    () => selectedKeys.map((k) => plans?.[k]).filter(Boolean),
    [selectedKeys, plans]
  );

  // Effektive Gruppen: aus den aktiven Plänen aggregieren.
  const rawGroups = useMemo(
    () => buildShoppingList(selectedPlans, recipes, ingredientCategories, categories),
    [selectedPlans, recipes, ingredientCategories, categories]
  );

  // Sichtbare Liste = Gruppen ohne bereits gekaufte Items
  const purchasedSet = purchasedItems instanceof Set ? purchasedItems : new Set();
  const effectiveGroups = useMemo(() => {
    return rawGroups
      .map((g) => ({
        ...g,
        items: g.items.filter((it) => !purchasedSet.has(itemKey(it))),
      }))
      .filter((g) => g.items.length > 0);
  }, [rawGroups, purchasedSet]);

  // Bereits gekaufte Items als flache Liste (für "Bereits gekauft"-Abschnitt)
  const purchasedListItems = useMemo(() => {
    const flat = [];
    for (const g of rawGroups) {
      for (const it of g.items) {
        if (purchasedSet.has(itemKey(it))) {
          flat.push({ ...it, _category: g.category });
        }
      }
    }
    return flat.sort((a, b) => a.name.localeCompare(b.name, "de"));
  }, [rawGroups, purchasedSet]);

  // Haupt-/Anker-Woche für Header-Status: die erste aktive Woche
  const primaryKey = selectedKeys[0] || toISODate(weekMonday);
  const primaryPlan = plans?.[primaryKey] || {
    weekStart: primaryKey,
    days: {},
    confirmedAt: null,
  };
  const isCombined = selectedKeys.length > 1;

  const toggleWeek = (k) => {
    setActiveKeys((prev) => {
      const n = new Set(prev);
      if (n.has(k)) {
        // Mindestens eine Woche muss aktiv bleiben
        if (n.size === 1) return prev;
        n.delete(k);
      } else {
        n.add(k);
      }
      return n;
    });
  };

  const toggle = (key) => {
    if (onToggleCheck) onToggleCheck(key);
  };

  // Verwendet die syncten Häkchen vom Server (über Workspace).
  // Fallback auf leeres Set, falls Prop fehlt (defensive coding).
  const checked = shoppingChecks instanceof Set ? shoppingChecks : new Set();

  const flatList = flattenGroups(effectiveGroups);
  const text = formatShoppingListText(effectiveGroups);

  // Item-Keys aller aktuell sichtbaren Zutaten (für Cleanup beim Abschluss)
  const visibleItemKeys = useMemo(() => flatList.map(itemKey), [flatList]);
  // Anzahl Items, die in der aktuellen Ansicht abgehakt sind
  const checkedCount = useMemo(
    () => visibleItemKeys.filter((k) => checked.has(k)).length,
    [visibleItemKeys, checked]
  );
  const totalCount = visibleItemKeys.length;

  // Beim Abschließen: aktuell abgehakte Items werden als „gekauft" markiert
  // → sie verschwinden aus der Liste, sind aber im "Bereits gekauft"-Bereich
  // weiterhin sichtbar/wiederherstellbar.
  const handleCompleteShopping = async (mode) => {
    setShowCompleteDialog(false);
    const checkedKeys = visibleItemKeys.filter((k) => checked.has(k));
    if (mode === "with_purchase") {
      if (onMarkPurchased) await onMarkPurchased(checkedKeys);
      if (onAddPurchase) onAddPurchase(selectedKeys);
      showToast("Zutaten als gekauft markiert – jetzt Betrag erfassen");
    } else if (mode === "without_purchase") {
      if (onMarkPurchased) await onMarkPurchased(checkedKeys);
      showToast(`${checkedKeys.length} ${checkedKeys.length === 1 ? "Zutat" : "Zutaten"} als gekauft markiert`);
    }
  };

  const handleCopy = async () => {
    try {
      if (navigator.clipboard && window.isSecureContext) {
        await navigator.clipboard.writeText(text);
        showToast("In Zwischenablage kopiert – in Bring! einfügen");
        return;
      }
      const ta = document.createElement("textarea");
      ta.value = text;
      ta.style.position = "fixed";
      ta.style.opacity = "0";
      document.body.appendChild(ta);
      ta.select();
      const ok = document.execCommand("copy");
      document.body.removeChild(ta);
      if (ok) {
        showToast("In Zwischenablage kopiert – in Bring! einfügen");
      } else {
        showToast("Kopieren nicht möglich – bitte Text manuell markieren", "err");
      }
    } catch (e) {
      console.error(e);
      showToast("Kopieren fehlgeschlagen", "err");
    }
  };

  const handleShare = async () => {
    if (navigator.share) {
      try {
        await navigator.share({
          title: isCombined
            ? `Einkauf ${selectedKeys.length} Wochen kombiniert`
            : `Einkauf KW ${getWeekNumber(new Date(primaryKey + "T00:00:00"))}`,
          text,
        });
      } catch {
        /* user cancelled */
      }
    } else {
      handleCopy();
    }
  };

  // Geplante Gerichte aus allen aktiv ausgewählten Wochen sammeln.
  const plannedEntries = useMemo(() => {
    const out = [];
    for (const k of selectedKeys) {
      const p = plans?.[k];
      if (!p) continue;
      const [y, m, d] = k.split("-").map(Number);
      const monday = new Date(y, m - 1, d);
      for (const day of DAYS) {
        const e = p.days?.[day.key];
        if (e?.recipeId) out.push({ weekKey: k, weekNr: getWeekNumber(monday), day, entry: e });
      }
    }
    return out;
  }, [selectedKeys, plans]);

  return (
    <div className="fade-in">
      {/* Header: Titel */}
      <div style={{ marginBottom: "0.6rem" }}>
        <h2 className="font-display" style={{ fontSize: "1.75rem", fontWeight: 600, margin: 0 }}>
          Einkaufsliste
        </h2>
        <div
          style={{
            color: "#6b6358",
            marginTop: "0.2rem",
            fontSize: "0.95rem",
          }}
        >
          {isCombined ? (
            <>
              <strong style={{ color: "#3d4a2a" }}>{selectedKeys.length} Wochen</strong>{" "}
              kombiniert ·{" "}
              {selectedKeys
                .map((k) => {
                  const [y, m, d] = k.split("-").map(Number);
                  return `KW ${getWeekNumber(new Date(y, m - 1, d))}`;
                })
                .join(" + ")}
            </>
          ) : (
            <>
              KW {getWeekNumber(new Date(primaryKey + "T00:00:00"))} ·{" "}
              {formatWeekRange(new Date(primaryKey + "T00:00:00"))}
              {primaryPlan.confirmedAt ? (
                <span style={{ color: "#3d4a2a", marginLeft: "0.4rem", fontWeight: 500 }}>
                  · ✓ bestätigt
                </span>
              ) : (
                <span style={{ color: "#c06a43", marginLeft: "0.4rem" }}>· Entwurf</span>
              )}
            </>
          )}
        </div>
      </div>

      {/* Wochenleiste – immer sichtbar, mehrere Wochen toggelbar */}
      {weekBar.length > 0 && (
        <div
          className="card"
          style={{
            padding: "0.7rem 0.9rem",
            marginBottom: "1rem",
          }}
        >
          <div
            style={{
              display: "flex",
              alignItems: "center",
              justifyContent: "space-between",
              gap: "0.5rem",
              marginBottom: "0.55rem",
              flexWrap: "wrap",
            }}
          >
            <div
              style={{
                fontSize: "0.78rem",
                letterSpacing: "0.06em",
                color: "#8a8275",
                textTransform: "uppercase",
                fontWeight: 600,
              }}
            >
              Wochen für deinen Einkauf
            </div>
            {weekBar.length > 1 && (
              <div style={{ fontSize: "0.78rem", color: "#8a8275" }}>
                Tippe weitere Wochen an, um zu kombinieren
              </div>
            )}
          </div>
          <div
            style={{
              display: "flex",
              gap: "0.4rem",
              overflowX: "auto",
              paddingBottom: "0.15rem",
              scrollbarWidth: "thin",
            }}
          >
            {weekBar.map((w) => {
              const active = activeKeys.has(w.key);
              const todayKey = toISODate(getMonday());
              const isCurrent = w.key === todayKey;
              const isFuture = w.key > todayKey;
              const isPast = w.key < todayKey;
              const relLabel = isCurrent
                ? "diese Woche"
                : isFuture && w.key === toISODate(addDays(getMonday(), 7))
                ? "nächste Woche"
                : isPast
                ? "vergangen"
                : null;
              return (
                <button
                  key={w.key}
                  onClick={() => toggleWeek(w.key)}
                  aria-pressed={active}
                  title={
                    active
                      ? `Aktiv – tippen zum Entfernen${
                          activeKeys.size === 1 ? " (mind. 1 Woche nötig)" : ""
                        }`
                      : "Diese Woche zum Einkauf hinzufügen"
                  }
                  style={{
                    flexShrink: 0,
                    display: "inline-flex",
                    flexDirection: "column",
                    alignItems: "flex-start",
                    gap: "0.15rem",
                    padding: "0.5rem 0.8rem",
                    minWidth: "8.5rem",
                    borderRadius: "0.7rem",
                    border: active ? "2px solid #3d4a2a" : "1px solid #d6cdb8",
                    background: active ? "rgba(61, 74, 42, 0.1)" : "#fdf8ec",
                    color: "#4a443b",
                    cursor: "pointer",
                    textAlign: "left",
                    transition: "all 0.15s ease",
                    opacity: isPast && !active ? 0.6 : 1,
                  }}
                >
                  <div
                    style={{
                      display: "flex",
                      alignItems: "center",
                      gap: "0.3rem",
                      fontSize: "0.82rem",
                      fontWeight: 600,
                      color: active ? "#3d4a2a" : "#4a443b",
                    }}
                  >
                    {active ? <Check size={13} /> : <Plus size={13} />}
                    KW {w.weekNr}
                    {w.confirmed && (
                      <span
                        title="bestätigt"
                        style={{ color: "#3d4a2a", fontSize: "0.7rem" }}
                      >
                        ✓
                      </span>
                    )}
                  </div>
                  <div
                    style={{
                      fontSize: "0.72rem",
                      color: "#8a8275",
                      lineHeight: 1.2,
                    }}
                  >
                    {formatWeekRange(w.monday)}
                  </div>
                  <div
                    style={{
                      fontSize: "0.7rem",
                      color: relLabel ? "#3d4a2a" : "#8a8275",
                      fontWeight: relLabel ? 500 : 400,
                    }}
                  >
                    {relLabel ? `${relLabel} · ` : ""}
                    {w.recipeCount} Rezept{w.recipeCount === 1 ? "" : "e"}
                  </div>
                </button>
              );
            })}
          </div>
        </div>
      )}


      {flatList.length === 0 ? (
        <div
          className="card"
          style={{ padding: "2.5rem", textAlign: "center", borderStyle: "dashed" }}
        >
          <ShoppingCart size={28} style={{ color: "#c06a43", margin: "0 auto 0.75rem" }} />
          <div style={{ fontWeight: 600, marginBottom: "0.25rem" }}>Leer</div>
          <div style={{ color: "#6b6358" }}>
            {plannedEntries.length === 0
              ? isCombined
                ? "Keine Rezepte in den ausgewählten Wochen geplant."
                : "Plane zuerst Rezepte im Wochenplan – hier erscheinen dann alle Zutaten zusammengefasst."
              : "Für die geplanten Rezepte wurden keine Zutaten gefunden."}
          </div>
        </div>
      ) : (
        <>
          <div className="card" style={{ padding: "0.9rem 1.1rem", marginBottom: "1rem" }}>
            <div
              style={{
                fontSize: "0.78rem",
                letterSpacing: "0.08em",
                color: "#8a8275",
                textTransform: "uppercase",
                fontWeight: 600,
                marginBottom: "0.5rem",
              }}
            >
              Geplante Gerichte
            </div>
            <div style={{ display: "flex", flexWrap: "wrap", gap: "0.4rem" }}>
              {plannedEntries.map(({ weekKey, weekNr, day, entry }) => {
                const r =
                  recipes.find((x) => x.id === entry.recipeId) ||
                  (entry.recipeSnapshot ? { name: entry.recipeSnapshot.name } : null);
                return (
                  <div
                    key={`${weekKey}-${day.key}`}
                    style={{
                      background: "rgba(61, 74, 42, 0.08)",
                      padding: "0.35rem 0.7rem",
                      borderRadius: "999px",
                      fontSize: "0.85rem",
                    }}
                  >
                    {isCombined && (
                      <span style={{ color: "#8a8275", marginRight: "0.3rem" }}>
                        KW{weekNr}
                      </span>
                    )}
                    <strong>{day.short}</strong> · {r?.name || "—"}{" "}
                    <span style={{ color: "#6b6358" }}>({entry.portions}P)</span>
                  </div>
                );
              })}
            </div>
          </div>

          <div style={{ display: "grid", gap: "0.75rem", marginBottom: "1rem" }}>
            {effectiveGroups.map((g, gi) => (
              <div key={g.category?.id || "uncat"} className="card" style={{ overflow: "hidden" }}>
                <div
                  style={{
                    padding: "0.6rem 1rem",
                    background: g.category ? "rgba(61, 74, 42, 0.06)" : "rgba(192, 106, 67, 0.05)",
                    borderBottom: "1px solid #ece4d2",
                    fontWeight: 600,
                    fontSize: "0.88rem",
                    display: "flex",
                    alignItems: "center",
                    gap: "0.45rem",
                    color: "#2a2620",
                  }}
                >
                  <span style={{ fontSize: "1.05rem" }}>
                    {g.category?.icon || "📦"}
                  </span>
                  <span>{g.category?.name || "Ohne Kategorie"}</span>
                  <span
                    style={{
                      marginLeft: "auto",
                      color: "#8a8275",
                      fontWeight: 400,
                      fontSize: "0.8rem",
                    }}
                  >
                    {g.items.length}
                  </span>
                </div>
                <ul style={{ listStyle: "none", padding: 0, margin: 0 }}>
                  {g.items.map((i, idx) => {
                    const key = itemKey(i);
                    const isChecked = checked.has(key);
                    const showCategoryPicker =
                      !i.categoryId || editingCategory === key;
                    return (
                      <li
                        key={key}
                        style={{
                          display: "flex",
                          alignItems: "center",
                          gap: "0.75rem",
                          padding: "0.55rem 1rem",
                          cursor: "pointer",
                          borderBottom: idx < g.items.length - 1 ? "1px solid #ece4d2" : "none",
                          opacity: isChecked ? 0.45 : 1,
                        }}
                        onClick={() => toggle(key)}
                      >
                        <div
                          style={{
                            width: "20px",
                            height: "20px",
                            borderRadius: "4px",
                            border: "1.5px solid " + (isChecked ? "#3d4a2a" : "#bfb5a3"),
                            background: isChecked ? "#3d4a2a" : "transparent",
                            display: "flex",
                            alignItems: "center",
                            justifyContent: "center",
                            flexShrink: 0,
                            transition: "all 0.15s",
                          }}
                        >
                          {isChecked && <Check size={14} color="#fdfaf3" />}
                        </div>
                        <div
                          style={{
                            flex: 1,
                            textDecoration: isChecked ? "line-through" : "none",
                          }}
                        >
                          {i.amount != null ? (
                            <>
                              <strong>
                                {prettyNumber(smartRoundAmount(i.amount, i.unit))} {i.unit}
                              </strong>{" "}
                              {i.name}
                            </>
                          ) : (
                            <>
                              {i.name}{" "}
                              <span style={{ color: "#8a8275" }}>
                                ({i.unit || "nach Bedarf"})
                              </span>
                            </>
                          )}
                          {Array.isArray(i.sources) && i.sources.length > 0 && (
                            <div
                              style={{
                                fontSize: "0.72rem",
                                color: "#8a8275",
                                marginTop: "0.15rem",
                                fontStyle: "italic",
                                lineHeight: 1.3,
                                textDecoration: "none",
                              }}
                            >
                              {(() => {
                                // Pro Rezept: alle Tage gesammelt → "Rezept (Mo · Mi)"
                                const byRecipe = new Map();
                                for (const s of i.sources) {
                                  const k = s.recipeId || s.recipeName;
                                  if (!byRecipe.has(k)) {
                                    byRecipe.set(k, {
                                      name: s.recipeName,
                                      days: new Set(),
                                    });
                                  }
                                  if (s.dayShort) byRecipe.get(k).days.add(s.dayShort);
                                }
                                const parts = [...byRecipe.values()].map((r) => {
                                  const d = [...r.days];
                                  return d.length > 0
                                    ? `${r.name} (${d.join(" · ")})`
                                    : r.name;
                                });
                                return `→ ${parts.join(", ")}`;
                              })()}
                            </div>
                          )}
                        </div>
                        {showCategoryPicker ? (
                          <select
                            value={ingredientCategories[i.name.toLowerCase()] || ""}
                            onChange={(e) => {
                              e.stopPropagation();
                              onAssignCategory(i.name, e.target.value || null);
                              setEditingCategory(null);
                            }}
                            onBlur={() => setEditingCategory(null)}
                            onClick={(e) => e.stopPropagation()}
                            autoFocus={editingCategory === key}
                            title="Kategorie zuweisen"
                            style={{
                              background: "transparent",
                              border: "1px solid #e4dccc",
                              borderRadius: "4px",
                              padding: "0.2rem 0.3rem",
                              fontSize: "0.75rem",
                              color: "#6b6358",
                              cursor: "pointer",
                              maxWidth: "7rem",
                            }}
                          >
                            <option value="">— Kategorie —</option>
                            {categories.map((c) => (
                              <option key={c.id} value={c.id}>
                                {c.icon} {c.name}
                              </option>
                            ))}
                          </select>
                        ) : (
                          <button
                            type="button"
                            onClick={(e) => {
                              e.stopPropagation();
                              setEditingCategory(key);
                            }}
                            title="Kategorie ändern"
                            aria-label="Kategorie ändern"
                            style={{
                              background: "transparent",
                              border: "none",
                              padding: "0.25rem",
                              borderRadius: "4px",
                              color: "#bfb5a3",
                              cursor: "pointer",
                              display: "inline-flex",
                              alignItems: "center",
                              justifyContent: "center",
                              opacity: 0.5,
                              transition: "opacity 0.15s, color 0.15s",
                            }}
                            onMouseEnter={(e) => {
                              e.currentTarget.style.opacity = "1";
                              e.currentTarget.style.color = "#6b6358";
                            }}
                            onMouseLeave={(e) => {
                              e.currentTarget.style.opacity = "0.5";
                              e.currentTarget.style.color = "#bfb5a3";
                            }}
                          >
                            <MoreVertical size={14} />
                          </button>
                        )}
                      </li>
                    );
                  })}
                </ul>
              </div>
            ))}
          </div>

          {/* ─── Bereits gekauft (klappbar) ────────────────────────── */}
          {purchasedListItems.length > 0 && (
            <div
              className="card"
              style={{
                padding: "0.4rem 0",
                marginBottom: "1rem",
                background: "#f5efe4",
                border: "1px solid #e4dccc",
                overflow: "hidden",
              }}
            >
              <button
                onClick={() => setShowPurchased((v) => !v)}
                style={{
                  width: "100%",
                  background: "transparent",
                  border: "none",
                  padding: "0.7rem 1rem",
                  cursor: "pointer",
                  fontFamily: "inherit",
                  fontSize: "0.9rem",
                  fontWeight: 600,
                  color: "#4a443b",
                  display: "flex",
                  alignItems: "center",
                  gap: "0.5rem",
                }}
              >
                <Archive size={15} style={{ color: "#8a8275" }} />
                Bereits gekauft ({purchasedListItems.length})
                {showPurchased ? (
                  <ChevronUp size={15} style={{ marginLeft: "auto", color: "#8a8275" }} />
                ) : (
                  <ChevronDown size={15} style={{ marginLeft: "auto", color: "#8a8275" }} />
                )}
              </button>
              {showPurchased && (
                <ul style={{ listStyle: "none", padding: 0, margin: 0 }}>
                  {purchasedListItems.map((i, idx) => (
                    <li
                      key={itemKey(i)}
                      style={{
                        display: "flex",
                        alignItems: "center",
                        gap: "0.6rem",
                        padding: "0.5rem 1rem",
                        borderTop: idx === 0 ? "1px solid #e4dccc" : "1px solid #ece4d2",
                        fontSize: "0.88rem",
                      }}
                    >
                      <Check size={14} style={{ color: "#3d4a2a", flexShrink: 0 }} />
                      <span
                        style={{
                          flex: 1,
                          color: "#6b6358",
                          textDecoration: "line-through",
                        }}
                      >
                        {i.amount != null
                          ? `${prettyNumber(smartRoundAmount(i.amount, i.unit))} ${i.unit} ${i.name}`
                          : `${i.name} (${i.unit || "nach Bedarf"})`}
                      </span>
                      <button
                        className="btn-ghost"
                        onClick={() => onRestorePurchased && onRestorePurchased(itemKey(i))}
                        title="Wiederherstellen – zurück auf die Liste"
                        style={{
                          padding: "0.3rem 0.55rem",
                          fontSize: "0.78rem",
                          color: "#c06a43",
                        }}
                      >
                        <Undo2 size={13} /> Zurück
                      </button>
                    </li>
                  ))}
                </ul>
              )}
            </div>
          )}

          {/* ─── Einkauf abschließen ──────────────────────────────── */}
          {checkedCount > 0 && (
            <div
              className="card"
              style={{
                padding: "1.1rem 1.25rem",
                marginBottom: "1rem",
                background: "rgba(61, 74, 42, 0.06)",
                border: "1.5px solid #3d4a2a",
              }}
            >
              <div
                style={{
                  display: "flex",
                  alignItems: "center",
                  gap: "0.75rem",
                  marginBottom: "0.7rem",
                  flexWrap: "wrap",
                }}
              >
                <div
                  style={{
                    background: "#3d4a2a",
                    color: "#fdfaf3",
                    borderRadius: "10px",
                    width: "38px",
                    height: "38px",
                    display: "inline-flex",
                    alignItems: "center",
                    justifyContent: "center",
                    flexShrink: 0,
                  }}
                >
                  <ListChecks size={19} />
                </div>
                <div style={{ flex: 1, minWidth: "10rem" }}>
                  <div
                    className="font-display"
                    style={{
                      fontSize: "1.1rem",
                      fontWeight: 600,
                      marginBottom: "0.1rem",
                    }}
                  >
                    Einkauf abschließen
                  </div>
                  <div style={{ color: "#6b6358", fontSize: "0.85rem" }}>
                    {checkedCount} von {totalCount}{" "}
                    {totalCount === 1 ? "Zutat" : "Zutaten"} abgehakt — bereit
                    zum Abschluss?
                  </div>
                </div>
              </div>

              <div style={{ display: "grid", gap: "0.4rem" }}>
                <button
                  className="btn-primary"
                  onClick={() => setShowCompleteDialog(true)}
                  style={{ justifyContent: "center" }}
                >
                  <Check size={16} /> Einkauf abschließen
                </button>
                <div
                  style={{
                    fontSize: "0.78rem",
                    color: "#8a8275",
                    textAlign: "center",
                    lineHeight: 1.4,
                  }}
                >
                  Abgehakte Zutaten verschwinden aus der Liste. Optional mit
                  Betrag-Erfassung für die Haushaltskasse.
                </div>
              </div>
            </div>
          )}

          <div
            className="card"
            style={{
              padding: "1.4rem 1.25rem",
              marginBottom: "1rem",
              background: "linear-gradient(135deg, #fdfaf3 0%, #f8f3e8 100%)",
              border: "1.5px solid #e4dccc",
            }}
          >
            <div
              style={{
                display: "flex",
                alignItems: "center",
                gap: "0.75rem",
                marginBottom: "0.9rem",
              }}
            >
              <div
                style={{
                  background: "#c06a43",
                  color: "#fdfaf3",
                  borderRadius: "10px",
                  width: "38px",
                  height: "38px",
                  display: "inline-flex",
                  alignItems: "center",
                  justifyContent: "center",
                  flexShrink: 0,
                }}
              >
                <ShoppingCart size={19} />
              </div>
              <div style={{ flex: 1 }}>
                <div
                  className="font-display"
                  style={{ fontSize: "1.15rem", fontWeight: 600, marginBottom: "0.1rem" }}
                >
                  Einkauf übertragen
                </div>
                <div style={{ color: "#6b6358", fontSize: "0.88rem" }}>
                  Nach Kategorien sortiert – fertig für Bring!, WhatsApp oder Notizen.
                </div>
              </div>
            </div>

            <div style={{ display: "grid", gap: "0.4rem" }}>
              {typeof navigator !== "undefined" && navigator.share ? (
                <button className="btn-primary" onClick={handleShare} style={{ justifyContent: "center" }}>
                  <Share2 size={16} /> Teilen – Bring! aus Menü wählen
                </button>
              ) : null}

              <button className="btn-secondary" onClick={handleCopy} style={{ justifyContent: "center" }}>
                <Copy size={16} /> Liste in Zwischenablage kopieren
              </button>

              <a
                href={`https://wa.me/?text=${encodeURIComponent(text)}`}
                target="_blank"
                rel="noopener noreferrer"
                className="btn-ghost"
                style={{
                  justifyContent: "center",
                  textDecoration: "none",
                  color: "#6b6358",
                  border: "1px solid transparent",
                  padding: "0.5rem 0.9rem",
                  borderRadius: "8px",
                  fontSize: "0.88rem",
                }}
              >
                <Share2 size={14} /> Per WhatsApp senden
              </a>

              {onAddPurchase && (
                <button
                  className="btn-ghost"
                  onClick={() => onAddPurchase(selectedKeys)}
                  style={{
                    justifyContent: "center",
                    border: "1px dashed #c06a43",
                    color: "#c06a43",
                    padding: "0.55rem 0.9rem",
                    borderRadius: "8px",
                    fontSize: "0.88rem",
                    marginTop: "0.2rem",
                  }}
                  title="Bezahlten Betrag für die Haushaltskasse erfassen"
                >
                  <Receipt size={14} /> Bezahlten Betrag erfassen
                </button>
              )}
            </div>

            <details style={{ marginTop: "0.9rem" }}>
              <summary
                style={{
                  cursor: "pointer",
                  color: "#6b6358",
                  fontSize: "0.82rem",
                  userSelect: "none",
                  padding: "0.25rem 0",
                }}
              >
                So kommt die Liste in Bring!
              </summary>
              <ol
                style={{
                  marginTop: "0.5rem",
                  color: "#4a443b",
                  fontSize: "0.85rem",
                  paddingLeft: "1.2rem",
                  lineHeight: 1.55,
                }}
              >
                <li>
                  <strong>Am Handy:</strong> Oben auf {'"'}Teilen{'"'} tippen → <strong>Bring!</strong>{" "}
                  aus dem System-Menü wählen. Bring! erkennt Zeilenumbrüche und legt pro Zeile
                  einen Eintrag an.
                </li>
                <li>
                  <strong>Am PC:</strong> {'"'}Liste in Zwischenablage kopieren{'"'} → in der Bring!-
                  Web-App oder am Handy Bring! öffnen → <strong>{'"'}+{'"'} → {'"'}Eintrag hinzufügen{'"'}</strong>{" "}
                  → Text einfügen.
                </li>
                <li>
                  Kategorie-Überschriften werden von Bring! nicht als Einträge erkannt – du kannst
                  sie vor dem Einfügen entfernen oder stehen lassen; Bring! ignoriert sie.
                </li>
              </ol>
            </details>
          </div>

          {!isCombined && primaryPlan.confirmedAt && (
            <div style={{ textAlign: "center" }}>
              <button className="btn-ghost" onClick={() => onUnconfirm(primaryKey)}>
                Zurück zum Bearbeiten
              </button>
            </div>
          )}
        </>
      )}

      {showCompleteDialog && (
        <div
          onClick={() => setShowCompleteDialog(false)}
          style={{
            position: "fixed",
            inset: 0,
            background: "rgba(0,0,0,0.45)",
            zIndex: 200,
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            padding: "1rem",
          }}
        >
          <div
            onClick={(e) => e.stopPropagation()}
            className="scale-in"
            style={{
              background: "#fdfaf3",
              borderRadius: "14px",
              width: "100%",
              maxWidth: "26rem",
              boxShadow: "0 20px 60px rgba(0,0,0,0.25)",
              overflow: "hidden",
            }}
          >
            <div
              style={{
                padding: "1.1rem 1.3rem",
                borderBottom: "1px solid #e4dccc",
                background: "#f8f3e8",
                display: "flex",
                alignItems: "center",
                gap: "0.6rem",
              }}
            >
              <ListChecks size={20} style={{ color: "#3d4a2a" }} />
              <h2
                className="font-display"
                style={{ margin: 0, fontSize: "1.2rem", fontWeight: 600 }}
              >
                Einkauf abschließen
              </h2>
            </div>
            <div style={{ padding: "1.2rem 1.3rem" }}>
              <p
                style={{
                  margin: "0 0 1rem",
                  color: "#4a443b",
                  fontSize: "0.92rem",
                  lineHeight: 1.5,
                }}
              >
                {checkedCount} {checkedCount === 1 ? "Zutat" : "Zutaten"} werden
                als <strong>gekauft</strong> markiert und aus der Liste entfernt.
                Möchtest du auch direkt den <strong>bezahlten Betrag</strong>{" "}
                für die Haushaltskasse erfassen?
              </p>
              <div style={{ display: "grid", gap: "0.5rem" }}>
                <button
                  className="btn-primary"
                  onClick={() => handleCompleteShopping("with_purchase")}
                  style={{ justifyContent: "center" }}
                >
                  <Receipt size={15} /> Ja – Betrag erfassen
                </button>
                <button
                  className="btn-secondary"
                  onClick={() => handleCompleteShopping("without_purchase")}
                  style={{ justifyContent: "center" }}
                >
                  Nur Liste leeren
                </button>
                <button
                  className="btn-ghost"
                  onClick={() => setShowCompleteDialog(false)}
                  style={{ justifyContent: "center" }}
                >
                  Abbrechen
                </button>
              </div>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// History View
// ────────────────────────────────────────────────────────────────────
function HistoryView({ plans, recipes, viewWeek, setViewWeek, purchases = [], members = [] }) {
  const entries = Object.entries(plans)
    .map(([k, v]) => ({ weekStart: k, ...v }))
    .filter((p) => Object.values(p.days || {}).some((d) => d?.recipeId))
    .sort((a, b) => b.weekStart.localeCompare(a.weekStart));

  // Pro Wochenstart: Summe der erfassten Einkäufe (in cents)
  const purchasesByWeek = useMemo(() => {
    const map = {};
    for (const p of purchases) {
      for (const ws of p.week_starts || []) {
        if (!map[ws]) map[ws] = { total: 0, count: 0 };
        map[ws].total += p.amount_cents || 0;
        map[ws].count += 1;
      }
    }
    return map;
  }, [purchases]);

  const memberById = useMemo(
    () => Object.fromEntries(members.map((m) => [m.userId, m])),
    [members]
  );

  const fmtCurrency = (cents) =>
    new Intl.NumberFormat("de-DE", {
      style: "currency",
      currency: "EUR",
      minimumFractionDigits: 2,
    }).format((cents || 0) / 100);

  if (viewWeek) {
    const plan = plans[viewWeek];
    const monday = new Date(viewWeek);
    return (
      <div className="fade-in">
        <button
          className="btn-ghost"
          onClick={() => setViewWeek(null)}
          style={{ marginBottom: "1rem" }}
        >
          <ChevronLeft size={16} /> Zurück zur Übersicht
        </button>
        <h2
          className="font-display"
          style={{ fontSize: "1.6rem", fontWeight: 600, margin: "0 0 0.2rem" }}
        >
          KW {getWeekNumber(monday)} · {formatWeekRange(monday)}
        </h2>
        <div style={{ color: "#6b6358", marginBottom: "1.25rem" }}>
          {plan.confirmedAt ? (
            <>Bestätigt am {new Date(plan.confirmedAt).toLocaleDateString("de-DE")}</>
          ) : (
            <>Entwurf (nicht bestätigt)</>
          )}
        </div>
        <div style={{ display: "grid", gap: "0.6rem" }}>
          {DAYS.map((d, i) => {
            const e = plan.days?.[d.key];
            const r = e?.recipeId
              ? recipes.find((x) => x.id === e.recipeId) ||
                (e.recipeSnapshot ? { name: e.recipeSnapshot.name } : null)
              : null;
            const date = addDays(monday, i);
            return (
              <div
                key={d.key}
                className="card"
                style={{
                  padding: "0.85rem 1.1rem",
                  display: "flex",
                  alignItems: "center",
                  gap: "1rem",
                  borderLeft: r ? "3px solid #c06a43" : "1.5px solid #e4dccc",
                }}
              >
                <div style={{ textAlign: "center", minWidth: "3rem" }}>
                  <div
                    style={{
                      fontSize: "0.7rem",
                      color: "#8a8275",
                      textTransform: "uppercase",
                      letterSpacing: "0.1em",
                      fontWeight: 600,
                    }}
                  >
                    {d.short}
                  </div>
                  <div
                    className="font-display"
                    style={{ fontSize: "1.2rem", fontWeight: 600, lineHeight: 1 }}
                  >
                    {date.getDate()}
                  </div>
                </div>
                <div style={{ flex: 1 }}>
                  {r ? (
                    <>
                      <div style={{ fontWeight: 500 }}>{r.name}</div>
                      <div style={{ color: "#6b6358", fontSize: "0.85rem" }}>
                        <Users
                          size={12}
                          style={{ display: "inline-block", verticalAlign: "-0.1em", marginRight: "0.25rem" }}
                        />
                        {e.portions} Portionen
                      </div>
                    </>
                  ) : (
                    <span style={{ color: "#bfb5a3", fontStyle: "italic" }}>—</span>
                  )}
                </div>
              </div>
            );
          })}
        </div>

        {/* Erfasste Einkäufe für diese Woche */}
        {(() => {
          const weekPurchases = purchases.filter((pu) =>
            (pu.week_starts || []).includes(viewWeek)
          );
          if (weekPurchases.length === 0) return null;
          const total = weekPurchases.reduce((s, p) => s + (p.amount_cents || 0), 0);
          const byPayer = {};
          for (const wp of weekPurchases) {
            byPayer[wp.paid_by] = (byPayer[wp.paid_by] || 0) + wp.amount_cents;
          }
          return (
            <div
              className="card"
              style={{
                padding: "1rem 1.1rem",
                marginTop: "1.25rem",
                borderLeft: "3px solid #3d4a2a",
              }}
            >
              <div
                style={{
                  display: "flex",
                  alignItems: "center",
                  gap: "0.5rem",
                  marginBottom: "0.6rem",
                }}
              >
                <Receipt size={16} style={{ color: "#3d4a2a" }} />
                <h3
                  className="font-display"
                  style={{ margin: 0, fontSize: "1.05rem", fontWeight: 600 }}
                >
                  Einkäufe diese Woche
                </h3>
                <span
                  style={{
                    marginLeft: "auto",
                    fontWeight: 600,
                    fontVariantNumeric: "tabular-nums",
                  }}
                >
                  {fmtCurrency(total)}
                </span>
              </div>
              <div style={{ display: "grid", gap: "0.35rem" }}>
                {Object.entries(byPayer).map(([uid, cents]) => {
                  const m = memberById[uid];
                  const label = m
                    ? (m.displayName?.trim() || (m.email ? m.email.split("@")[0] : uid.slice(0, 6)))
                    : uid.slice(0, 6);
                  return (
                    <div
                      key={uid}
                      style={{
                        display: "flex",
                        justifyContent: "space-between",
                        fontSize: "0.9rem",
                      }}
                    >
                      <span>{label}</span>
                      <span style={{ fontVariantNumeric: "tabular-nums", color: "#6b6358" }}>
                        {fmtCurrency(cents)}
                      </span>
                    </div>
                  );
                })}
              </div>
            </div>
          );
        })()}
      </div>
    );
  }

  return (
    <div className="fade-in">
      <h2 className="font-display" style={{ fontSize: "1.75rem", fontWeight: 600, margin: "0 0 0.2rem" }}>
        Verlauf
      </h2>
      <div style={{ color: "#6b6358", marginBottom: "1.25rem" }}>
        Alle bisher geplanten Wochen – rückwärts nach Datum sortiert.
      </div>

      {entries.length === 0 ? (
        <div
          className="card"
          style={{ padding: "2.5rem", textAlign: "center", borderStyle: "dashed" }}
        >
          <HistoryIcon size={28} style={{ color: "#c06a43", margin: "0 auto 0.75rem" }} />
          <div style={{ fontWeight: 600, marginBottom: "0.25rem" }}>Noch kein Verlauf</div>
          <div style={{ color: "#6b6358" }}>
            Sobald du eine Woche planst, erscheint sie hier.
          </div>
        </div>
      ) : (
        <div style={{ display: "grid", gap: "0.6rem" }}>
          {entries.map((p) => {
            const monday = new Date(p.weekStart);
            const count = Object.values(p.days).filter((d) => d?.recipeId).length;
            const names = Object.entries(p.days)
              .filter(([, d]) => d?.recipeId)
              .map(([, d]) =>
                recipes.find((r) => r.id === d.recipeId)?.name ||
                d.recipeSnapshot?.name ||
                null
              )
              .filter(Boolean);
            return (
              <button
                key={p.weekStart}
                onClick={() => setViewWeek(p.weekStart)}
                className="card"
                style={{
                  padding: "1rem 1.1rem",
                  textAlign: "left",
                  cursor: "pointer",
                  background: "#fdfaf3",
                  border: "1.5px solid #e4dccc",
                  transition: "all 0.15s",
                }}
                onMouseEnter={(e) => {
                  e.currentTarget.style.borderColor = "#3d4a2a";
                  e.currentTarget.style.transform = "translateY(-1px)";
                }}
                onMouseLeave={(e) => {
                  e.currentTarget.style.borderColor = "#e4dccc";
                  e.currentTarget.style.transform = "translateY(0)";
                }}
              >
                <div
                  style={{
                    display: "flex",
                    justifyContent: "space-between",
                    alignItems: "flex-start",
                    gap: "1rem",
                  }}
                >
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div
                      className="font-display"
                      style={{ fontSize: "1.15rem", fontWeight: 600, marginBottom: "0.15rem" }}
                    >
                      KW {getWeekNumber(monday)}{" "}
                      <span
                        style={{
                          color: "#8a8275",
                          fontWeight: 400,
                          fontStyle: "italic",
                          fontSize: "0.95rem",
                          marginLeft: "0.3rem",
                        }}
                      >
                        {formatWeekRange(monday)}
                      </span>
                    </div>
                    <div
                      style={{
                        color: "#6b6358",
                        fontSize: "0.85rem",
                        whiteSpace: "nowrap",
                        overflow: "hidden",
                        textOverflow: "ellipsis",
                      }}
                    >
                      {count} {count === 1 ? "Tag" : "Tage"}: {names.join(" · ")}
                    </div>
                    {purchasesByWeek[p.weekStart] && (
                      <div
                        style={{
                          color: "#3d4a2a",
                          fontSize: "0.82rem",
                          fontWeight: 500,
                          marginTop: "0.3rem",
                          display: "inline-flex",
                          alignItems: "center",
                          gap: "0.3rem",
                        }}
                      >
                        <Receipt size={12} />
                        {fmtCurrency(purchasesByWeek[p.weekStart].total)}
                        <span style={{ color: "#8a8275", fontWeight: 400 }}>
                          · {purchasesByWeek[p.weekStart].count}{" "}
                          {purchasesByWeek[p.weekStart].count === 1 ? "Einkauf" : "Einkäufe"}
                        </span>
                      </div>
                    )}
                  </div>
                  <div
                    style={{
                      display: "flex",
                      alignItems: "center",
                      gap: "0.4rem",
                      color: "#3d4a2a",
                      fontSize: "0.82rem",
                      fontWeight: 500,
                      flexShrink: 0,
                    }}
                  >
                    {p.confirmedAt ? (
                      <>
                        <ListChecks size={14} /> Bestätigt
                      </>
                    ) : (
                      <span style={{ color: "#c06a43" }}>Entwurf</span>
                    )}
                    <ArrowRight size={14} style={{ color: "#8a8275" }} />
                  </div>
                </div>
              </button>
            );
          })}
        </div>
      )}
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Auth-Screen
// ────────────────────────────────────────────────────────────────────
function AuthScreen({ onLogin, onRegister }) {
  const [mode, setMode] = useState("register");
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [displayName, setDisplayName] = useState("");
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState("");
  const [info, setInfo] = useState("");

  const handleSubmit = async (e) => {
    e.preventDefault();
    setError("");
    setInfo("");
    if (!email.trim() || !password) {
      setError("Bitte E-Mail und Passwort angeben.");
      return;
    }
    if (mode === "register" && !displayName.trim()) {
      setError("Bitte einen Anzeigenamen angeben.");
      return;
    }
    if (mode === "register" && password.length < 6) {
      setError("Passwort muss mindestens 6 Zeichen lang sein.");
      return;
    }
    setBusy(true);
    try {
      if (mode === "login") {
        await onLogin(email.trim(), password);
      } else {
        await onRegister(email.trim(), password, displayName.trim());
        setInfo(
          "Konto erstellt. Falls Supabase dir eine Bestätigungs-E-Mail schickt, bestätige sie – danach kannst du dich einloggen."
        );
        setMode("login");
        setPassword("");
      }
    } catch (e) {
      console.error(e);
      const msg = e?.message || "Unbekannter Fehler";
      if (/invalid login credentials/i.test(msg)) {
        setError("E-Mail oder Passwort falsch.");
      } else if (/already registered/i.test(msg) || /already exists/i.test(msg)) {
        setError("Für diese E-Mail existiert bereits ein Konto.");
      } else if (/email not confirmed/i.test(msg)) {
        setError("E-Mail wurde noch nicht bestätigt – prüfe dein Postfach.");
      } else {
        setError(msg);
      }
    } finally {
      setBusy(false);
    }
  };

  return (
    <div
      style={{
        minHeight: "100vh",
        background: "#f5efe4",
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        padding: "2rem 1rem",
      }}
    >
      <style>{`
        @import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,500;0,9..144,600;0,9..144,700;1,9..144,400&family=Instrument+Sans:wght@400;500;600&display=swap');
        .font-display { font-family: 'Fraunces', Georgia, serif; font-optical-sizing: auto; }
        body, input, button, select, textarea { font-family: 'Instrument Sans', system-ui, sans-serif; }
      `}</style>
      <div style={{ width: "100%", maxWidth: "26rem" }}>
        <div style={{ textAlign: "center", marginBottom: "1.2rem" }}>
          {/* Brand-Eyebrow: Logo + Wortmarke kompakt nebeneinander */}
          <div
            style={{
              display: "inline-flex",
              alignItems: "center",
              gap: "0.5rem",
              marginBottom: "0.95rem",
            }}
          >
            <LoeffelListeLogo size={32} />
            <span
              className="font-display"
              style={{
                fontSize: "1.05rem",
                fontWeight: 600,
                color: "#3d4a2a",
                letterSpacing: "-0.01em",
              }}
            >
              Löffelliste
            </span>
          </div>
          <h1
            className="font-display"
            style={{
              fontSize: "1.95rem",
              fontWeight: 600,
              margin: 0,
              letterSpacing: "-0.025em",
              lineHeight: 1.1,
              color: "#2a2620",
            }}
          >
            Nie wieder<br />
            <span style={{ fontStyle: "italic", color: "#3d4a2a" }}>
              „Was kochen wir heute?"
            </span>
          </h1>
          <p
            style={{
              color: "#6b6358",
              marginTop: "0.7rem",
              marginBottom: 0,
              fontSize: "0.95rem",
              fontWeight: 400,
              lineHeight: 1.45,
            }}
          >
            10 Minuten am Sonntag. Sieben entspannte Abende.<br />
            Bis zu{" "}
            <strong style={{ color: "#3d4a2a", fontWeight: 600 }}>
              200 €/Monat
            </strong>{" "}
            gespart gegenüber Lieferdiensten.
          </p>
        </div>

        {/* Vergleichs-Block: 3 Boxen mit Icons + integriertem Disclaimer */}
        <div
          style={{
            background: "#fdfaf3",
            border: "1.5px solid #e4dccc",
            borderRadius: "12px",
            padding: "1rem 0.9rem 0.85rem",
            marginBottom: 0,
            boxShadow: "0 4px 20px rgba(0,0,0,0.04)",
          }}
        >
          <div
            style={{
              fontSize: "0.68rem",
              fontWeight: 600,
              color: "#8a8275",
              textTransform: "uppercase",
              letterSpacing: "0.06em",
              marginBottom: "0.85rem",
              textAlign: "center",
            }}
          >
            Was kostet ein Abendessen für 2?
          </div>

          <div
            style={{
              display: "grid",
              gridTemplateColumns: "1fr 1fr 1fr",
              gap: "0.4rem",
            }}
          >
            {[
              { kind: "emoji", icon: "🛵", label: "Lieferdienst", price: "~50 €", highlight: false },
              { kind: "emoji", icon: "🤷", label: "Spontan", price: "~11 €", highlight: false },
              { kind: "logo", label: "Löffelliste", price: "~6 €", highlight: true },
            ].map((box, i) => (
              <div
                key={i}
                style={{
                  background: box.highlight
                    ? "linear-gradient(135deg, #3d4a2a 0%, #4a5a35 100%)"
                    : "rgba(245,239,228,0.5)",
                  border: box.highlight ? "1.5px solid #3d4a2a" : "1px solid #e4dccc",
                  borderRadius: "9px",
                  padding: "0.7rem 0.35rem",
                  textAlign: "center",
                  boxShadow: box.highlight ? "0 4px 14px rgba(61,74,42,0.22)" : "none",
                }}
              >
                <div
                  style={{
                    display: "flex",
                    justifyContent: "center",
                    alignItems: "center",
                    height: "2rem",
                    marginBottom: "0.4rem",
                  }}
                >
                  {box.kind === "logo" ? (
                    <LoeffelListeLogo size={32} />
                  ) : (
                    <span
                      style={{
                        fontSize: "1.75rem",
                        lineHeight: 1,
                        // Emoji-Rendering nicht von "font-display" beeinflussen
                        fontFamily:
                          "'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', sans-serif",
                      }}
                      role="img"
                      aria-label={box.label}
                    >
                      {box.icon}
                    </span>
                  )}
                </div>
                <div
                  style={{
                    fontSize: "0.6rem",
                    fontWeight: 600,
                    color: box.highlight ? "rgba(245,239,228,0.85)" : "#6b6358",
                    textTransform: "uppercase",
                    letterSpacing: "0.04em",
                    marginBottom: "0.35rem",
                    lineHeight: 1.1,
                  }}
                >
                  {box.label}
                </div>
                <div
                  className="font-display"
                  style={{
                    fontSize: "1.05rem",
                    fontWeight: 600,
                    color: box.highlight ? "#f5efe4" : "#2a2620",
                    lineHeight: 1,
                    letterSpacing: "-0.01em",
                  }}
                >
                  {box.price}
                </div>
              </div>
            ))}
          </div>

          <div
            style={{
              marginTop: "0.85rem",
              paddingTop: "0.65rem",
              borderTop: "1px solid #e4dccc",
              fontSize: "0.68rem",
              color: "#8a8275",
              textAlign: "center",
              lineHeight: 1.4,
            }}
          >
            Pro Abendessen für 2 Personen · Lieferdienst inkl. Liefer- & Servicegebühren
          </div>
        </div>

        {/* Trenner zum Anmelde-/Registrieren-Bereich */}
        <div
          style={{
            display: "flex",
            alignItems: "center",
            gap: "0.7rem",
            margin: "1.5rem 0 1.3rem",
          }}
        >
          <div style={{ flex: 1, height: "1px", background: "#e4dccc" }} />
          <span
            className="font-display"
            style={{
              fontSize: "0.85rem",
              color: "#6b6358",
              fontStyle: "italic",
              fontWeight: 500,
            }}
          >
            Klingt gut?
          </span>
          <div style={{ flex: 1, height: "1px", background: "#e4dccc" }} />
        </div>

        <form
          onSubmit={handleSubmit}
          style={{
            background: "#fdfaf3",
            border: "1.5px solid #e4dccc",
            borderRadius: "12px",
            padding: "1.6rem",
            boxShadow: "0 4px 20px rgba(0,0,0,0.04)",
          }}
        >
          <div
            style={{
              display: "flex",
              gap: "0.25rem",
              padding: "0.25rem",
              background: "rgba(61, 74, 42, 0.06)",
              borderRadius: "8px",
              marginBottom: "1.5rem",
            }}
          >
            {["login", "register"].map((m) => (
              <button
                key={m}
                type="button"
                onClick={() => {
                  setMode(m);
                  setError("");
                  setInfo("");
                }}
                style={{
                  flex: 1,
                  padding: "0.5rem",
                  background: mode === m ? "#fdfaf3" : "transparent",
                  color: mode === m ? "#3d4a2a" : "#6b6358",
                  fontWeight: mode === m ? 600 : 500,
                  border: "none",
                  borderRadius: "6px",
                  cursor: "pointer",
                  fontSize: "0.92rem",
                  transition: "all 0.15s",
                  boxShadow: mode === m ? "0 1px 3px rgba(0,0,0,0.06)" : "none",
                }}
              >
                {m === "login" ? "Anmelden" : "Registrieren"}
              </button>
            ))}
          </div>

          <div style={{ display: "grid", gap: "0.9rem" }}>
            {mode === "register" && (
              <div>
                <label style={{ ...labelStyle, textTransform: "none", fontSize: "0.85rem", letterSpacing: 0 }}>Anzeigename</label>
                <input
                  className="input-base"
                  style={{ width: "100%" }}
                  value={displayName}
                  onChange={(e) => setDisplayName(e.target.value)}
                  placeholder="z.B. Anna"
                  maxLength={24}
                  autoFocus
                />
              </div>
            )}
            <div>
              <label style={{ ...labelStyle, textTransform: "none", fontSize: "0.85rem", letterSpacing: 0 }}>E-Mail</label>
              <input
                className="input-base"
                style={{ width: "100%" }}
                type="email"
                value={email}
                onChange={(e) => setEmail(e.target.value)}
                placeholder="du@example.com"
                autoComplete="email"
                autoFocus={mode === "login"}
              />
            </div>
            <div>
              <label style={{ ...labelStyle, textTransform: "none", fontSize: "0.85rem", letterSpacing: 0 }}>Passwort</label>
              <input
                className="input-base"
                style={{ width: "100%" }}
                type="password"
                value={password}
                onChange={(e) => setPassword(e.target.value)}
                placeholder={mode === "register" ? "mindestens 6 Zeichen" : "••••••••"}
                autoComplete={mode === "login" ? "current-password" : "new-password"}
              />
            </div>
          </div>

          {error && (
            <div
              style={{
                marginTop: "1rem",
                padding: "0.65rem 0.85rem",
                background: "rgba(168, 66, 42, 0.08)",
                border: "1px solid rgba(168, 66, 42, 0.25)",
                borderRadius: "6px",
                color: "#a8422a",
                fontSize: "0.87rem",
                display: "flex",
                alignItems: "flex-start",
                gap: "0.5rem",
              }}
            >
              <AlertCircle size={15} style={{ flexShrink: 0, marginTop: "0.1rem" }} />
              <span>{error}</span>
            </div>
          )}

          {info && (
            <div
              style={{
                marginTop: "1rem",
                padding: "0.65rem 0.85rem",
                background: "rgba(61, 74, 42, 0.08)",
                border: "1px solid rgba(61, 74, 42, 0.25)",
                borderRadius: "6px",
                color: "#3d4a2a",
                fontSize: "0.87rem",
                display: "flex",
                alignItems: "flex-start",
                gap: "0.5rem",
              }}
            >
              <Info size={15} style={{ flexShrink: 0, marginTop: "0.1rem" }} />
              <span>{info}</span>
            </div>
          )}

          <button
            type="submit"
            disabled={busy}
            style={{
              marginTop: "1.5rem",
              width: "100%",
              padding: "0.75rem",
              background: "#3d4a2a",
              color: "#f5efe4",
              border: "none",
              borderRadius: "8px",
              fontWeight: 600,
              fontSize: "0.95rem",
              cursor: busy ? "wait" : "pointer",
              opacity: busy ? 0.6 : 1,
              display: "inline-flex",
              alignItems: "center",
              justifyContent: "center",
              gap: "0.5rem",
              transition: "background 0.15s",
            }}
          >
            {mode === "login" ? <LogIn size={16} /> : <UserPlus size={16} />}
            {busy
              ? mode === "login"
                ? "Melde an…"
                : "Erstelle Konto…"
              : mode === "login"
              ? "Anmelden"
              : "Kostenlos starten"}
          </button>

          <div
            style={{
              marginTop: "1.25rem",
              fontSize: "0.8rem",
              color: "#8a8275",
              textAlign: "center",
              lineHeight: 1.5,
            }}
          >
            {mode === "login" ? (
              <>
                Noch kein Konto?{" "}
                <button
                  type="button"
                  onClick={() => {
                    setMode("register");
                    setError("");
                    setInfo("");
                  }}
                  style={{
                    background: "none",
                    border: "none",
                    color: "#3d4a2a",
                    cursor: "pointer",
                    fontWeight: 600,
                    padding: 0,
                    fontSize: "0.8rem",
                    fontFamily: "inherit",
                  }}
                >
                  Jetzt kostenlos registrieren
                </button>
              </>
            ) : (
              <>
                Schon ein Konto?{" "}
                <button
                  type="button"
                  onClick={() => {
                    setMode("login");
                    setError("");
                    setInfo("");
                  }}
                  style={{
                    background: "none",
                    border: "none",
                    color: "#3d4a2a",
                    cursor: "pointer",
                    fontWeight: 600,
                    padding: 0,
                    fontSize: "0.8rem",
                    fontFamily: "inherit",
                  }}
                >
                  Anmelden
                </button>
              </>
            )}
          </div>
        </form>

        <div style={{ marginTop: "0.8rem" }}>
          <AuthIntro />
        </div>
      </div>
    </div>
  );
}


// ────────────────────────────────────────────────────────────────────
// CookView – Reste-Koch-Finder
// ────────────────────────────────────────────────────────────────────
function CookView({
  recipes,
  publicRecipes,
  publicRecipesLoadedAt,
  onLoadPublic,
  onStartPlanPublic,
  weekMonday,
  plan,
  onPlanDay,
  onGoToShopping,
  onOpenRecipe,
  showToast,
}) {
  const [adHoc, setAdHoc] = useState([]);
  const [newIngredient, setNewIngredient] = useState("");
  const [strictMode, setStrictMode] = useState(false);
  const [useStaples, setUseStaples] = useState(true);
  const [includePublic, setIncludePublic] = useState(false);
  const [planPickerFor, setPlanPickerFor] = useState(null);
  const inputRef = useRef(null);

  // Autofokus beim Öffnen des Tabs
  useEffect(() => {
    if (inputRef.current) inputRef.current.focus();
  }, []);

  const addAdHoc = () => {
    const clean = newIngredient.trim();
    if (!clean) return;
    if (adHoc.some((i) => normalizeIngredientName(i.name) === normalizeIngredientName(clean))) {
      setNewIngredient("");
      return;
    }
    setAdHoc((prev) => [...prev, { name: clean }]);
    setNewIngredient("");
    if (inputRef.current) inputRef.current.focus();
  };

  const removeAdHoc = (name) => {
    setAdHoc((prev) => prev.filter((i) => i.name !== name));
  };

  const availableNames = useMemo(() => {
    const seen = new Set();
    const out = [];
    for (const a of adHoc) {
      const key = a.name.toLowerCase().trim();
      if (!key || seen.has(key)) continue;
      seen.add(key);
      out.push(a.name);
    }
    return out;
  }, [adHoc]);

  const suggestions = useMemo(() => getIngredientSuggestions(recipes), [recipes]);

  // Klick auf Pill: direkt als Ad-hoc hinzufügen
  const pickSuggestion = (name) => {
    const clean = name.trim();
    if (!clean) return;
    if (
      adHoc.some((i) => normalizeIngredientName(i.name) === normalizeIngredientName(clean))
    )
      return;
    setAdHoc((prev) => [...prev, { name: clean }]);
  };

  const matches = useMemo(() => {
    if (availableNames.length === 0) return [];
    const ownResults = recipes.map((r) => ({
      recipe: r,
      isPublic: false,
      ...matchRecipe(r, availableNames, useStaples),
    }));
    const publicResults = includePublic
      ? publicRecipes.map((r) => ({
          recipe: r,
          isPublic: true,
          ...matchRecipe(r, availableNames, useStaples),
        }))
      : [];
    const results = [...ownResults, ...publicResults];
    const filtered = results
      .filter((m) => m.total > 0)
      .filter((m) => (strictMode ? m.missing.length === 0 : m.score >= 0.6));
    filtered.sort((a, b) => {
      if (b.score !== a.score) return b.score - a.score;
      // Eigene Rezepte bei Gleichstand bevorzugen
      if (a.isPublic !== b.isPublic) return a.isPublic ? 1 : -1;
      return a.recipe.name.localeCompare(b.recipe.name, "de");
    });
    return filtered;
  }, [recipes, publicRecipes, includePublic, availableNames, useStaples, strictMode]);

  // Lade öffentliche Rezepte, sobald der Toggle aktiviert wird
  useEffect(() => {
    if (includePublic && !publicRecipesLoadedAt && onLoadPublic) {
      onLoadPublic();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [includePublic]);

  const perfectMatches = matches.filter((m) => m.missing.length === 0);
  const nearMatches = matches.filter((m) => m.missing.length > 0);

  return (
    <div className="fade-in">
      <div style={{ marginBottom: "1rem" }}>
        <h2
          className="font-display"
          style={{ fontSize: "1.75rem", fontWeight: 600, margin: "0 0 0.2rem 0" }}
        >
          Was kann ich heute kochen?
        </h2>
        <div style={{ color: "#6b6358" }}>
          Trag ein, was du zuhause hast – wir durchsuchen deine Rezepte.
        </div>
      </div>

      <div
        className="card"
        style={{
          padding: "1.1rem 1.2rem",
          marginBottom: "1rem",
          background: "linear-gradient(135deg, #fdfaf3 0%, #f8f3e8 100%)",
        }}
      >
        {/* Prominente Eingabe zuerst */}
        <label
          htmlFor="cook-input"
          style={{
            display: "block",
            fontSize: "0.78rem",
            letterSpacing: "0.08em",
            color: "#8a8275",
            textTransform: "uppercase",
            fontWeight: 600,
            marginBottom: "0.5rem",
          }}
        >
          Deine Zutaten
        </label>
        <div style={{ display: "flex", gap: "0.45rem", alignItems: "center" }}>
          <IngredientAutocomplete
            id="cook-input"
            inputRef={inputRef}
            value={newIngredient}
            onChange={setNewIngredient}
            onEnter={() => addAdHoc()}
            suggestions={suggestions.map((s) => s.name)}
            placeholder="Zutat tippen – z.B. Tomaten, Nudeln, Feta …"
            style={{ flex: 1 }}
          />
          <button
            className="btn-primary"
            onClick={addAdHoc}
            disabled={!newIngredient.trim()}
            style={{ padding: "0.7rem 1rem" }}
            aria-label="Zutat hinzufügen"
          >
            <Plus size={16} />
          </button>
        </div>

        <IngredientSuggestionPills
          suggestions={suggestions}
          excludeNames={availableNames}
          filter={newIngredient}
          onPick={pickSuggestion}
        />

        {/* Gewählte Zutaten */}
        {availableNames.length > 0 && (
          <div
            style={{
              marginTop: "0.9rem",
              paddingTop: "0.75rem",
              borderTop: "1px dashed #e4dccc",
            }}
          >
            <div
              style={{
                fontSize: "0.78rem",
                letterSpacing: "0.06em",
                color: "#8a8275",
                textTransform: "uppercase",
                fontWeight: 600,
                marginBottom: "0.5rem",
              }}
            >
              Gewählt ({availableNames.length})
            </div>
            <div
              style={{
                display: "flex",
                flexWrap: "wrap",
                gap: "0.4rem",
              }}
            >
              {adHoc.map((a) => (
                <span
                  key={`a-${a.name}`}
                  className="pill"
                  style={{
                    background: "rgba(61, 74, 42, 0.1)",
                    color: "#3d4a2a",
                    border: "1px solid rgba(61, 74, 42, 0.25)",
                    padding: "0.3rem 0.55rem 0.3rem 0.7rem",
                    fontWeight: 500,
                  }}
                >
                  {a.name}
                  <button
                    onClick={() => removeAdHoc(a.name)}
                    aria-label={`${a.name} entfernen`}
                    style={{
                      background: "transparent",
                      border: "none",
                      color: "inherit",
                      cursor: "pointer",
                      padding: "0",
                      marginLeft: "0.3rem",
                      display: "inline-flex",
                      opacity: 0.7,
                    }}
                  >
                    <X size={12} />
                  </button>
                </span>
              ))}
            </div>
          </div>
        )}
      </div>

      {availableNames.length > 0 && (
        <div
          style={{
            display: "flex",
            gap: "0.5rem",
            flexWrap: "wrap",
            marginBottom: "1rem",
          }}
        >
          <button
            onClick={() => setStrictMode(false)}
            className={!strictMode ? "btn-primary" : "btn-secondary"}
            style={{ padding: "0.45rem 0.9rem", fontSize: "0.85rem" }}
          >
            Auch fast passend (≥ 60 %)
          </button>
          <button
            onClick={() => setStrictMode(true)}
            className={strictMode ? "btn-primary" : "btn-secondary"}
            style={{ padding: "0.45rem 0.9rem", fontSize: "0.85rem" }}
          >
            Nur komplett passend
          </button>
          <label
            style={{
              display: "inline-flex",
              alignItems: "center",
              gap: "0.4rem",
              fontSize: "0.85rem",
              color: "#6b6358",
              padding: "0.45rem 0.8rem",
              background: "rgba(61, 74, 42, 0.04)",
              border: "1px solid #e4dccc",
              borderRadius: "8px",
              cursor: "pointer",
              userSelect: "none",
            }}
            title="Salz, Pfeffer, Öl, Wasser, Butter, Zucker, Mehl, Essig werden als immer vorhanden angenommen."
          >
            <input
              type="checkbox"
              checked={useStaples}
              onChange={(e) => setUseStaples(e.target.checked)}
              style={{ margin: 0, width: "auto" }}
            />
            Grundzutaten annehmen
          </label>
          <label
            style={{
              display: "inline-flex",
              alignItems: "center",
              gap: "0.4rem",
              fontSize: "0.85rem",
              color: includePublic ? "#4a6b8a" : "#6b6358",
              padding: "0.45rem 0.8rem",
              background: includePublic ? "rgba(74, 107, 138, 0.08)" : "rgba(61, 74, 42, 0.04)",
              border: `1px solid ${includePublic ? "rgba(74, 107, 138, 0.3)" : "#e4dccc"}`,
              borderRadius: "8px",
              cursor: "pointer",
              userSelect: "none",
            }}
            title="Auch öffentlich geteilte Rezepte anderer Nutzer:innen durchsuchen."
          >
            <input
              type="checkbox"
              checked={includePublic}
              onChange={(e) => setIncludePublic(e.target.checked)}
              style={{ margin: 0, width: "auto" }}
            />
            <Sparkles size={13} /> Auch Entdecken
          </label>
        </div>
      )}

      {availableNames.length === 0 ? (
        <div
          className="card"
          style={{ padding: "2.5rem", textAlign: "center", borderStyle: "dashed" }}
        >
          <Utensils size={28} style={{ color: "#c06a43", margin: "0 auto 0.75rem" }} />
          <div style={{ fontWeight: 600, marginBottom: "0.25rem" }}>
            Keine Zutaten eingegeben
          </div>
          <div style={{ color: "#6b6358" }}>
            Trag oben ein, was du zuhause hast – wir suchen dann passende Rezepte.
          </div>
        </div>
      ) : matches.length === 0 ? (
        <div
          className="card"
          style={{ padding: "2.5rem", textAlign: "center", borderStyle: "dashed" }}
        >
          <Search size={28} style={{ color: "#c06a43", margin: "0 auto 0.75rem" }} />
          <div style={{ fontWeight: 600, marginBottom: "0.25rem" }}>
            {strictMode ? "Kein Rezept komplett möglich" : "Keine Treffer"}
          </div>
          <div style={{ color: "#6b6358" }}>
            {strictMode
              ? 'Schalte auf "Auch fast passend" um, um Rezepte zu sehen, bei denen nur wenige Zutaten fehlen.'
              : 'Mit diesen Zutaten können wir gerade kein Rezept ausreichend erfüllen.'}
          </div>
        </div>
      ) : (
        <>
          {perfectMatches.length > 0 && (
            <div style={{ marginBottom: "1.5rem" }}>
              <div
                style={{
                  fontSize: "0.78rem",
                  letterSpacing: "0.08em",
                  color: "#3d4a2a",
                  textTransform: "uppercase",
                  fontWeight: 600,
                  marginBottom: "0.6rem",
                  display: "flex",
                  alignItems: "center",
                  gap: "0.4rem",
                }}
              >
                <Check size={14} /> Komplett kochbar ({perfectMatches.length})
              </div>
              <div style={{ display: "grid", gap: "0.75rem" }}>
                {perfectMatches.map((m) => (
                  <CookMatchCard
                    key={`${m.isPublic ? "pub" : "own"}-${m.recipe.id}`}
                    match={m}
                    plan={plan}
                    weekMonday={weekMonday}
                    planPickerFor={planPickerFor}
                    setPlanPickerFor={setPlanPickerFor}
                    onPlanDay={onPlanDay}
                    onGoToShopping={onGoToShopping}
                    onOpenRecipe={onOpenRecipe}
                    onStartPlanPublic={onStartPlanPublic}
                    showToast={showToast}
                  />
                ))}
              </div>
            </div>
          )}

          {nearMatches.length > 0 && (
            <div>
              <div
                style={{
                  fontSize: "0.78rem",
                  letterSpacing: "0.08em",
                  color: "#c06a43",
                  textTransform: "uppercase",
                  fontWeight: 600,
                  marginBottom: "0.6rem",
                  display: "flex",
                  alignItems: "center",
                  gap: "0.4rem",
                }}
              >
                <AlertCircle size={14} /> Fast möglich ({nearMatches.length})
              </div>
              <div style={{ display: "grid", gap: "0.75rem" }}>
                {nearMatches.map((m) => (
                  <CookMatchCard
                    key={`${m.isPublic ? "pub" : "own"}-${m.recipe.id}`}
                    match={m}
                    plan={plan}
                    weekMonday={weekMonday}
                    planPickerFor={planPickerFor}
                    setPlanPickerFor={setPlanPickerFor}
                    onPlanDay={onPlanDay}
                    onGoToShopping={onGoToShopping}
                    onOpenRecipe={onOpenRecipe}
                    onStartPlanPublic={onStartPlanPublic}
                    showToast={showToast}
                  />
                ))}
              </div>
            </div>
          )}
        </>
      )}
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// CookMatchCard
// ────────────────────────────────────────────────────────────────────
function CookMatchCard({
  match,
  plan,
  weekMonday,
  planPickerFor,
  setPlanPickerFor,
  onPlanDay,
  onGoToShopping,
  onOpenRecipe,
  onStartPlanPublic,
  showToast,
}) {
  const { recipe, score, missing, have, total, isPublic } = match;
  const cat = getRecipeCategory(recipe.recipeCategory);
  const percent = Math.round(score * 100);
  const isPerfect = missing.length === 0;

  const handlePlanOnDay = (dayKey) => {
    onPlanDay(dayKey, {
      recipeId: recipe.id,
      portions: recipe.basePortions,
    });
    setPlanPickerFor(null);
    const dayLabel = DAYS.find((d) => d.key === dayKey)?.label || dayKey;
    showToast(`"${recipe.name}" auf ${dayLabel} eingeplant`);
  };

  return (
    <div
      className="card"
      style={{
        padding: "1rem 1.1rem",
        borderLeft: `3px solid ${isPerfect ? "#3d4a2a" : "#c06a43"}`,
      }}
    >
      <div
        style={{
          display: "flex",
          justifyContent: "space-between",
          gap: "0.75rem",
          alignItems: "flex-start",
          marginBottom: "0.6rem",
        }}
      >
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ display: "flex", gap: "0.35rem", marginBottom: "0.35rem", flexWrap: "wrap" }}>
            <div
              className="pill"
              style={{
                background: `${cat.color}15`,
                color: cat.color,
              }}
            >
              <span>{cat.icon}</span> {cat.label}
            </div>
            {isPublic && (
              <div
                className="pill"
                style={{
                  background: "rgba(74, 107, 138, 0.12)",
                  color: "#4a6b8a",
                }}
                title={`Von ${recipe.createdBy || "einem anderen Nutzer"} öffentlich geteilt`}
              >
                <Sparkles size={11} /> Entdecken
              </div>
            )}
          </div>
          <h3
            className="font-display"
            onClick={() => (isPublic && onStartPlanPublic ? onStartPlanPublic(recipe) : onOpenRecipe(recipe.id))}
            style={{
              fontSize: "1.15rem",
              fontWeight: 600,
              lineHeight: 1.25,
              margin: 0,
              cursor: "pointer",
            }}
            title={isPublic ? "Rezept-Details öffnen & einplanen" : "Rezept-Details öffnen"}
          >
            {recipe.name}
          </h3>
          {isPublic && recipe.createdBy && (
            <div style={{ fontSize: "0.78rem", color: "#4a6b8a", marginTop: "0.2rem" }}>
              geteilt von {recipe.createdBy}
            </div>
          )}
        </div>
        <div
          style={{
            flexShrink: 0,
            textAlign: "center",
            padding: "0.3rem 0.6rem",
            borderRadius: "10px",
            background: isPerfect ? "rgba(61, 74, 42, 0.1)" : "rgba(192, 106, 67, 0.1)",
            color: isPerfect ? "#3d4a2a" : "#a8422a",
            minWidth: "3.5rem",
          }}
        >
          <div
            className="font-display"
            style={{ fontSize: "1.25rem", fontWeight: 600, lineHeight: 1 }}
          >
            {percent}%
          </div>
          <div style={{ fontSize: "0.7rem", letterSpacing: "0.05em", marginTop: "0.15rem" }}>
            {have}/{total}
          </div>
        </div>
      </div>

      {missing.length > 0 && (
        <div
          style={{
            background: "rgba(192, 106, 67, 0.05)",
            border: "1px dashed rgba(192, 106, 67, 0.3)",
            borderRadius: "8px",
            padding: "0.6rem 0.8rem",
            marginBottom: "0.75rem",
          }}
        >
          <div
            style={{
              fontSize: "0.78rem",
              fontWeight: 600,
              color: "#a8422a",
              marginBottom: "0.35rem",
              letterSpacing: "0.02em",
            }}
          >
            Dir fehlt noch:
          </div>
          <div style={{ display: "flex", flexWrap: "wrap", gap: "0.3rem" }}>
            {missing.map((ing, i) => (
              <span
                key={i}
                className="pill"
                style={{
                  background: "#fdfaf3",
                  color: "#4a443b",
                  border: "1px solid #e4dccc",
                  padding: "0.2rem 0.55rem",
                  fontWeight: 500,
                }}
              >
                {ing.amount ? `${prettyNumber(ing.amount)} ${ing.unit} ` : ""}
                {ing.name}
              </span>
            ))}
          </div>
        </div>
      )}

      <div style={{ display: "flex", gap: "0.4rem", flexWrap: "wrap" }}>
        {isPublic ? (
          <>
            <button
              className="btn-ghost"
              onClick={() => onStartPlanPublic && onStartPlanPublic(recipe)}
              style={{ fontSize: "0.85rem", color: "#4a6b8a" }}
            >
              <BookOpen size={14} /> Ansehen
            </button>
            <button
              className="btn-secondary"
              onClick={() => onStartPlanPublic && onStartPlanPublic(recipe)}
              style={{ fontSize: "0.85rem" }}
            >
              <Calendar size={14} /> Einplanen
            </button>
          </>
        ) : (
          <>
            <button
              className="btn-ghost"
              onClick={() => onOpenRecipe(recipe.id)}
              style={{ fontSize: "0.85rem" }}
            >
              <BookOpen size={14} /> Rezept ansehen
            </button>
            <div style={{ position: "relative" }}>
              <button
                className="btn-secondary"
                onClick={() =>
                  setPlanPickerFor(planPickerFor === recipe.id ? null : recipe.id)
                }
                style={{ fontSize: "0.85rem" }}
              >
                <Calendar size={14} /> Einplanen
              </button>
          {planPickerFor === recipe.id && (
            <>
              <div
                style={{ position: "fixed", inset: 0, zIndex: 40 }}
                onClick={() => setPlanPickerFor(null)}
              />
              <div
                className="scale-in"
                style={{
                  position: "absolute",
                  left: 0,
                  top: "100%",
                  marginTop: "0.4rem",
                  background: "#fdfaf3",
                  border: "1.5px solid #e4dccc",
                  borderRadius: "10px",
                  boxShadow: "0 10px 30px rgba(0,0,0,0.12)",
                  zIndex: 41,
                  padding: "0.35rem",
                  minWidth: "14rem",
                }}
              >
              <div
                  style={{
                    fontSize: "0.72rem",
                    letterSpacing: "0.08em",
                    color: "#8a8275",
                    textTransform: "uppercase",
                    fontWeight: 600,
                    padding: "0.4rem 0.6rem 0.3rem",
                  }}
                >
                  Auf welchen Tag?
                </div>
                {DAYS.map((d, i) => {
                  const date = addDays(weekMonday, i);
                  const isOccupied = !!plan.days?.[d.key]?.recipeId;
                  return (
                    <button
                      key={d.key}
                      onClick={() => handlePlanOnDay(d.key)}
                      style={{
                        display: "flex",
                        alignItems: "center",
                        gap: "0.55rem",
                        width: "100%",
                        padding: "0.5rem 0.65rem",
                        background: "transparent",
                        border: "none",
                        cursor: "pointer",
                        textAlign: "left",
                        borderRadius: "6px",
                        fontFamily: "inherit",
                        fontSize: "0.9rem",
                        color: "#2a2620",
                        transition: "background 0.12s",
                      }}
                      onMouseEnter={(e) => {
                        e.currentTarget.style.background = "rgba(61, 74, 42, 0.08)";
                      }}
                      onMouseLeave={(e) => {
                        e.currentTarget.style.background = "transparent";
                      }}
                    >
                      <div
                        style={{
                          minWidth: "2.1rem",
                          textAlign: "center",
                          padding: "0.1rem 0.3rem",
                          background: "rgba(61, 74, 42, 0.06)",
                          borderRadius: "5px",
                        }}
                      >
                        <div
                          style={{
                            fontSize: "0.62rem",
                            color: "#8a8275",
                            textTransform: "uppercase",
                            fontWeight: 600,
                          }}
                        >
                          {d.short}
                        </div>
                        <div
                          className="font-display"
                          style={{ fontSize: "0.9rem", fontWeight: 600, lineHeight: 1 }}
                        >
                          {date.getDate()}
                        </div>
                      </div>
                      <div style={{ flex: 1 }}>
                        <div>{d.label}</div>
                        {isOccupied && (
                          <div
                            style={{
                              fontSize: "0.72rem",
                              color: "#c06a43",
                              fontStyle: "italic",
                            }}
                          >
                            belegt – wird überschrieben
                          </div>
                        )}
                      </div>
                    </button>
                  );
                })}
              </div>
            </>
          )}
        </div>
          </>
        )}
        {missing.length > 0 && (
          <button
            className="btn-ghost"
            onClick={onGoToShopping}
            style={{ fontSize: "0.85rem" }}
          >
            <ShoppingCart size={14} /> Fehlendes einkaufen
          </button>
        )}
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Auth-Intro – Aufklappbares Info-Panel unter dem Formular
// ────────────────────────────────────────────────────────────────────
function AuthIntro() {
  const [open, setOpen] = useState(false);
  const features = [
    {
      icon: <Calendar size={15} />,
      title: "Wochenplan",
      desc: "Wer kocht wann was – auch allein.",
    },
    {
      icon: <BookOpen size={15} />,
      title: "340.000+ Rezepte",
      desc: "Komplette Chefkoch-Datenbank + Community.",
    },
    {
      icon: <ShoppingCart size={15} />,
      title: "Einkaufsliste",
      desc: "Automatisch aus dem Wochenplan.",
    },
    {
      icon: <Utensils size={15} />,
      title: "Reste-Koch",
      desc: "Rezepte aus dem, was du zu Hause hast.",
    },
    {
      icon: <Users size={15} />,
      title: "Haushalt teilen",
      desc: "Live-Sync mit Familie/WG in Echtzeit.",
    },
  ];

  return (
    <div
      style={{
        background: "#fdfaf3",
        border: "1.5px solid #e4dccc",
        borderRadius: "12px",
        padding: "0.85rem 1rem",
        boxShadow: "0 4px 20px rgba(0,0,0,0.04)",
      }}
    >
      <button
        type="button"
        onClick={() => setOpen((v) => !v)}
        aria-expanded={open}
        style={{
          width: "100%",
          display: "flex",
          alignItems: "center",
          justifyContent: "space-between",
          background: "none",
          border: "none",
          cursor: "pointer",
          padding: 0,
          fontFamily: "inherit",
          textAlign: "left",
        }}
      >
        <div style={{ display: "flex", alignItems: "center", gap: "0.55rem" }}>
          <div
            style={{
              width: "1.9rem",
              height: "1.9rem",
              borderRadius: "7px",
              background: "rgba(61, 74, 42, 0.1)",
              color: "#3d4a2a",
              display: "flex",
              alignItems: "center",
              justifyContent: "center",
              flexShrink: 0,
            }}
          >
            <ChefHat size={16} />
          </div>
          <span
            className="font-display"
            style={{
              fontSize: "0.98rem",
              fontWeight: 600,
              color: "#2a2620",
            }}
          >
            Was kann Löffelliste?
          </span>
        </div>
        {open ? (
          <ChevronUp size={17} style={{ color: "#6b6358" }} />
        ) : (
          <ChevronDown size={17} style={{ color: "#6b6358" }} />
        )}
      </button>

      {open && (
        <>
          <div
            style={{
              marginTop: "0.85rem",
              paddingTop: "0.85rem",
              borderTop: "1px solid #e4dccc",
              display: "grid",
              gap: "0.7rem",
            }}
          >
            {features.map((f, i) => (
              <div
                key={i}
                style={{
                  display: "flex",
                  gap: "0.65rem",
                  alignItems: "flex-start",
                }}
              >
                <div
                  style={{
                    flexShrink: 0,
                    marginTop: "0.1rem",
                    width: "1.75rem",
                    height: "1.75rem",
                    borderRadius: "6px",
                    background: "rgba(61, 74, 42, 0.1)",
                    color: "#3d4a2a",
                    display: "flex",
                    alignItems: "center",
                    justifyContent: "center",
                  }}
                >
                  {f.icon}
                </div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div
                    style={{
                      fontWeight: 600,
                      color: "#2a2620",
                      fontSize: "0.88rem",
                      lineHeight: 1.25,
                    }}
                  >
                    {f.title}
                  </div>
                  <div
                    style={{
                      fontSize: "0.8rem",
                      color: "#6b6358",
                      lineHeight: 1.35,
                      marginTop: "0.1rem",
                    }}
                  >
                    {f.desc}
                  </div>
                </div>
              </div>
            ))}
          </div>

          <div
            style={{
              marginTop: "0.85rem",
              paddingTop: "0.7rem",
              borderTop: "1px solid #e4dccc",
              fontSize: "0.78rem",
              color: "#8a8275",
              textAlign: "center",
              letterSpacing: "0.01em",
            }}
          >
            Kostenlos · ohne Abo · werbefrei
          </div>
        </>
      )}
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// HelpModal – Anleitung für eingeloggte User (Fragezeichen oben rechts)
// ────────────────────────────────────────────────────────────────────
function HelpModal({ onClose, userId }) {
  const sections = [
    {
      icon: <Calendar size={18} />,
      title: "Wochenplan",
      body: (
        <>
          Im Tab <strong>Wochenplan</strong> weist du jedem Wochentag ein Rezept zu.
          Tippe auf einen Tag → Rezept auswählen → Portionen einstellen. Mit
          „<em>Woche bestätigen</em>“ wird der Plan abgeschlossen, die
          Nutzungshäufigkeit aktualisiert und die Woche landet im Verlauf.
        </>
      ),
    },
    {
      icon: <BookOpen size={18} />,
      title: "Rezepte",
      body: (
        <>
          Lege eigene Rezepte an oder greife auf die komplette{" "}
          <strong>Chefkoch-Datenbank</strong> sowie auf Rezepte anderer{" "}
          <strong>App-Nutzer</strong> zu. Markiere Favoriten mit dem Stern, nutze
          Kategorien (Hauptgang, Suppe, Dessert …) und teile deine eigenen
          Rezepte optional mit der Community.
        </>
      ),
    },
    {
      icon: <ShoppingCart size={18} />,
      title: "Einkaufsliste",
      body: (
        <>
          Die Einkaufsliste wird <strong>automatisch</strong> aus deinem
          Wochenplan erzeugt und nach Kategorien (Obst, Milchprodukte …)
          gruppiert. Unter jeder Zutat siehst du,{" "}
          <strong>welches geplante Gericht</strong> sie braucht. Häkchen werden
          live mit allen Mitgliedern synchronisiert. Mit{" "}
          <strong>„Einkauf abschließen"</strong> verschwinden die abgehakten
          Zutaten aus der Liste — nicht abgehakte bleiben sichtbar (so siehst
          du, was für welches Gericht noch fehlt). Versehentlich abgehakte
          findest du im Bereich <em>„Bereits gekauft"</em> und kannst sie
          zurückholen.
        </>
      ),
    },
    {
      icon: <Utensils size={18} />,
      title: "Reste-Koch",
      body: (
        <>
          Gib ein, was du da hast – Löffelliste zeigt dir Rezepte, die du damit
          (fast) kochen kannst. Mit „<em>strikt</em>“ nur 100 %-Treffer, sonst auch
          Rezepte mit wenigen fehlenden Zutaten.
        </>
      ),
    },
    {
      icon: <Users size={18} />,
      title: "Haushalt teilen",
      body: (
        <>
          Profilmenü (Avatar oben rechts) → <strong>Arbeitsbereich</strong>.
          Dort findest du deinen Einladungslink für deinen Haushalt. Alle
          Mitglieder sehen dieselben Rezepte, Pläne und Einkaufslisten in Echtzeit.
        </>
      ),
    },
    {
      icon: <Wallet size={18} />,
      title: "Haushaltskasse",
      body: (
        <>
          Erfasse pro Einkauf den <strong>bezahlten Betrag</strong> und wer
          ausgelegt hat – auf Wunsch auch mit <strong>Quittungs-Foto</strong>{" "}
          (per Kamera oder aus der Galerie). Der Tab <strong>Kasse</strong>{" "}
          zeigt dir Salden pro Mitglied und einen{" "}
          <strong>optimalen Ausgleichs-Vorschlag</strong> (minimale Anzahl
          Zahlungen). Über „<em>Bezahlt</em>" markierst du eine Ausgleichszahlung
          als geleistet. Splitmodi: <em>Gleich</em>, <em>Anteile</em>,{" "}
          <em>Exakt</em> oder <em>nur Bezahler</em>.
        </>
      ),
    },
    {
      icon: <HistoryIcon size={18} />,
      title: "Verlauf",
      body: (
        <>
          Bestätigte Wochen landen im Verlauf. So siehst du, was ihr wann gekocht
          habt, und kannst beliebte Wochen erneut als Vorlage verwenden.
        </>
      ),
    },
    {
      icon: <RefreshCw size={18} />,
      title: "Synchronisation",
      body: (
        <>
          Die App synchronisiert automatisch. Mit dem{" "}
          <RefreshCw size={13} style={{ verticalAlign: "-2px" }} />-Button oben
          erzwingst du eine Sofort-Aktualisierung.
        </>
      ),
    },
  ];

  return (
    <div
      onClick={onClose}
      style={{
        position: "fixed",
        inset: 0,
        background: "rgba(0,0,0,0.45)",
        zIndex: 200,
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        padding: "1rem",
      }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        className="scale-in"
        style={{
          background: "#fdfaf3",
          borderRadius: "14px",
          width: "100%",
          maxWidth: "36rem",
          maxHeight: "90vh",
          overflow: "hidden",
          display: "flex",
          flexDirection: "column",
          boxShadow: "0 20px 60px rgba(0,0,0,0.25)",
        }}
      >
        <div
          style={{
            padding: "1.1rem 1.3rem",
            borderBottom: "1px solid #e4dccc",
            display: "flex",
            alignItems: "center",
            justifyContent: "space-between",
            background: "#f8f3e8",
          }}
        >
          <div style={{ display: "flex", alignItems: "center", gap: "0.6rem" }}>
            <HelpCircle size={20} style={{ color: "#3d4a2a" }} />
            <h2
              className="font-display"
              style={{ margin: 0, fontSize: "1.25rem", fontWeight: 600 }}
            >
              Anleitung & Hilfe
            </h2>
          </div>
          <button className="icon-btn" onClick={onClose} aria-label="Schließen">
            <X size={18} />
          </button>
        </div>

        <div style={{ overflowY: "auto", padding: "1.2rem 1.3rem" }}>
          <p
            style={{
              margin: "0 0 1.2rem",
              fontSize: "0.92rem",
              color: "#4a443b",
              lineHeight: 1.55,
            }}
          >
            Willkommen bei Löffelliste! Hier findest du die wichtigsten Funktionen
            im Überblick. Die App ist komplett kostenlos – kein Abo, keine
            versteckten Premium-Features.
          </p>

          <div style={{ display: "grid", gap: "1rem" }}>
            {sections.map((s, i) => (
              <div
                key={i}
                style={{
                  background: "#f5efe4",
                  border: "1px solid #e4dccc",
                  borderRadius: "10px",
                  padding: "0.9rem 1rem",
                }}
              >
                <div
                  style={{
                    display: "flex",
                    alignItems: "center",
                    gap: "0.55rem",
                    marginBottom: "0.45rem",
                  }}
                >
                  <div
                    style={{
                      width: "2rem",
                      height: "2rem",
                      borderRadius: "7px",
                      background: "rgba(61, 74, 42, 0.12)",
                      color: "#3d4a2a",
                      display: "flex",
                      alignItems: "center",
                      justifyContent: "center",
                      flexShrink: 0,
                    }}
                  >
                    {s.icon}
                  </div>
                  <h3
                    className="font-display"
                    style={{ margin: 0, fontSize: "1.05rem", fontWeight: 600 }}
                  >
                    {s.title}
                  </h3>
                </div>
                <div
                  style={{
                    fontSize: "0.9rem",
                    color: "#4a443b",
                    lineHeight: 1.55,
                  }}
                >
                  {s.body}
                </div>
              </div>
            ))}
          </div>

          <div
            style={{
              marginTop: "1.2rem",
              padding: "0.8rem 1rem",
              background: "rgba(61, 74, 42, 0.08)",
              border: "1px solid rgba(61, 74, 42, 0.2)",
              borderRadius: "8px",
              fontSize: "0.85rem",
              color: "#3d4a2a",
              lineHeight: 1.5,
            }}
          >
            💡 <strong>Tipp:</strong> Du erreichst diese Anleitung jederzeit wieder
            über das <HelpCircle size={13} style={{ verticalAlign: "-2px" }} />-Symbol
            oben rechts.
          </div>
        </div>

        <div
          style={{
            padding: "0.9rem 1.3rem",
            borderTop: "1px solid #e4dccc",
            display: "flex",
            justifyContent: "space-between",
            alignItems: "center",
            gap: "0.5rem",
            flexWrap: "wrap",
            background: "#f8f3e8",
          }}
        >
          <button
            onClick={() => {
              if (userId) {
                resetTour(userId);
                onClose();
                setTimeout(() => window.location.reload(), 100);
              }
            }}
            style={{
              padding: "0.5rem 0.9rem",
              background: "transparent",
              color: "#3d4a2a",
              border: "1px solid #3d4a2a",
              borderRadius: "8px",
              fontWeight: 500,
              fontSize: "0.85rem",
              cursor: "pointer",
              fontFamily: "inherit",
              display: "inline-flex",
              alignItems: "center",
              gap: "0.4rem",
            }}
            title="Setzt die Tour zurück und startet sie neu"
          >
            <RefreshCw size={13} /> Tour neu starten
          </button>
          <button
            onClick={onClose}
            style={{
              padding: "0.6rem 1.3rem",
              background: "#3d4a2a",
              color: "#f5efe4",
              border: "none",
              borderRadius: "8px",
              fontWeight: 600,
              fontSize: "0.9rem",
              cursor: "pointer",
              fontFamily: "inherit",
            }}
          >
            Verstanden
          </button>
        </div>
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Haushaltskasse – Helper-Funktionen
// ────────────────────────────────────────────────────────────────────
const fmtMoney = (cents, currency = "EUR") =>
  new Intl.NumberFormat("de-DE", {
    style: "currency",
    currency,
    minimumFractionDigits: 2,
  }).format((cents || 0) / 100);

const fmtPurchaseDate = (iso) => {
  if (!iso) return "";
  return new Date(iso).toLocaleDateString("de-DE", {
    day: "2-digit",
    month: "short",
    year: "numeric",
  });
};

const memberDisplayName = (m) => {
  if (!m) return "Unbekannt";
  if (m.displayName && m.displayName.trim()) return m.displayName.trim();
  if (m.email) return m.email.split("@")[0];
  if (m.userId) return m.userId.slice(0, 6);
  return "Unbekannt";
};

// Salden-Berechnung: positiv = bekommt Geld zurück, negativ = schuldet noch
function computeBalances(purchases, settlements, members) {
  const bal = Object.fromEntries(members.map((m) => [m.userId, 0]));
  const memberIds = new Set(members.map((m) => m.userId));

  for (const p of purchases) {
    const total = Number(p.amount_cents) || 0;
    if (total === 0) continue;
    if (memberIds.has(p.paid_by)) bal[p.paid_by] += total;

    const excluded = new Set(p.excluded_members || []);
    const participants = members
      .map((m) => m.userId)
      .filter((id) => !excluded.has(id));

    if (p.split_mode === "payer_only" || participants.length === 0) continue;

    if (p.split_mode === "equal") {
      const base = Math.floor(total / participants.length);
      const remainder = total - base * participants.length;
      participants.forEach((id, i) => {
        bal[id] -= base + (i < remainder ? 1 : 0);
      });
    } else if (p.split_mode === "shares") {
      const sd = p.split_data || {};
      const sum = Object.values(sd).reduce(
        (a, b) => a + (Number(b) || 0),
        0
      );
      if (sum <= 0) continue;
      const entries = Object.entries(sd);
      let assigned = 0;
      entries.forEach(([uid, s], idx) => {
        const share =
          idx === entries.length - 1
            ? total - assigned
            : Math.round((total * Number(s)) / sum);
        if (memberIds.has(uid)) bal[uid] -= share;
        assigned += share;
      });
    } else if (p.split_mode === "exact") {
      for (const [uid, cents] of Object.entries(p.split_data || {})) {
        if (memberIds.has(uid)) bal[uid] -= Number(cents) || 0;
      }
    }
  }

  for (const s of settlements) {
    const amt = Number(s.amount_cents) || 0;
    if (memberIds.has(s.from_user)) bal[s.from_user] += amt;
    if (memberIds.has(s.to_user)) bal[s.to_user] -= amt;
  }
  return bal;
}

// Min-Cashflow-Algorithmus: erzeugt minimale Anzahl Ausgleichs-Transaktionen
function settleSuggestions(balances) {
  const debtors = Object.entries(balances)
    .filter(([, v]) => v < -1)
    .map(([k, v]) => [k, v])
    .sort((a, b) => a[1] - b[1]);
  const creditors = Object.entries(balances)
    .filter(([, v]) => v > 1)
    .map(([k, v]) => [k, v])
    .sort((a, b) => b[1] - a[1]);
  const tx = [];
  let i = 0,
    j = 0;
  let safety = 0;
  while (i < debtors.length && j < creditors.length && safety++ < 1000) {
    const pay = Math.min(-debtors[i][1], creditors[j][1]);
    if (pay <= 0) break;
    tx.push({ from: debtors[i][0], to: creditors[j][0], amount_cents: pay });
    debtors[i][1] += pay;
    creditors[j][1] -= pay;
    if (Math.abs(debtors[i][1]) < 1) i++;
    if (Math.abs(creditors[j][1]) < 1) j++;
  }
  return tx;
}

// ────────────────────────────────────────────────────────────────────
// Settlement-Cycle-Helper
// Modell: Letztes Settlement = "Bücher geschlossen". Alle Käufe, die
// VOR diesem Zeitpunkt im System eingetragen wurden, gelten als
// abgerechnet — alle danach als offen.
//
// WICHTIG: Wir vergleichen NICHT `purchased_at` (das Kaufdatum, das
// der User frei wählt), sondern den Eintragungs-Zeitpunkt im System
// (`created_at` falls vorhanden, sonst `updated_at`).
// Sonst würde ein heute eingetragener Kauf, datiert auf "heute 12:00",
// fälschlicherweise als abgerechnet gelten, wenn das Settlement später
// am Tag erfolgte.
// ────────────────────────────────────────────────────────────────────
function getLatestSettlementTime(settlements) {
  if (!settlements?.length) return null;
  let latest = "";
  for (const s of settlements) {
    const t = s.settled_at || "";
    if (t > latest) latest = t;
  }
  return latest || null;
}

// Wann wurde der Kauf im System eingetragen (nicht gleich Kaufdatum)?
function getPurchaseEntryTime(purchase) {
  if (!purchase) return "";
  return purchase.created_at || purchase.updated_at || purchase.purchased_at || "";
}

function isPurchaseSettled(purchase, cutoffISO) {
  if (!cutoffISO || !purchase) return false;
  const entryTime = getPurchaseEntryTime(purchase);
  if (!entryTime) return false;
  return entryTime < cutoffISO;
}

// Label für eine Runde. Wenn am selben Tag mehrere Runden existieren,
// wird die Uhrzeit ergänzt — sonst nur das Datum.
function formatRoundLabel(round, allRounds) {
  if (!round?.firstAt) return "";
  const sameDayCount = allRounds.filter((r) => r.day === round.day).length;
  const date = new Date(round.firstAt);
  const dateLabel = date.toLocaleDateString("de-DE", {
    day: "numeric",
    month: "short",
    year: "numeric",
  });
  if (sameDayCount <= 1) return dateLabel;
  const timeLabel = date.toLocaleTimeString("de-DE", {
    hour: "2-digit",
    minute: "2-digit",
  });
  return `${dateLabel}, ${timeLabel} Uhr`;
}

// Gruppiert Settlements pro „Abrechnungs-Runde". Eine Runde umfasst alle
// Settlements, die innerhalb von 1 Sekunde erzeugt wurden — typischerweise
// passiert das nur bei Mehrfach-Inserts aus einem einzigen User-Trigger.
// Settlements aus separaten Klicks → separate Runden, auch am selben Tag.
const ROUND_CLUSTER_MS = 1000;

function groupSettlementsByRound(settlements) {
  if (!settlements?.length) return [];
  const sorted = [...settlements]
    .filter((s) => s.settled_at)
    .sort((a, b) => a.settled_at.localeCompare(b.settled_at));
  const rounds = [];
  for (const s of sorted) {
    const t = new Date(s.settled_at).getTime();
    const current = rounds[rounds.length - 1];
    if (
      current &&
      t - new Date(current.maxAt).getTime() < ROUND_CLUSTER_MS
    ) {
      current.items.push(s);
      current.maxAt = s.settled_at;
    } else {
      rounds.push({
        items: [s],
        firstAt: s.settled_at,
        maxAt: s.settled_at,
      });
    }
  }
  // DESC: neueste Runde zuerst
  return rounds
    .map((r) => ({
      id: r.firstAt, // stabile ID (ISO-Timestamp der ersten Zahlung)
      items: r.items,
      firstAt: r.firstAt,
      maxAt: r.maxAt,
      day: r.firstAt.slice(0, 10),
      total: r.items.reduce((sum, x) => sum + (x.amount_cents || 0), 0),
    }))
    .reverse();
}

// Ordnet jede abgerechnete Position genau einer Runde zu — der ersten
// Runde (chronologisch), deren Zeitpunkt nach dem Eintragungs-
// Zeitpunkt der Position liegt.
//   Position eingetragen T_p  →  gehört zur kleinsten Runde mit maxAt > T_p
// Rückgabe: { byId: { roundId -> Position[] }, unassigned: Position[] }
function assignPurchasesToRounds(settledPurchases, rounds) {
  const byId = {};
  const unassigned = [];
  for (const r of rounds) byId[r.id] = [];
  if (!rounds.length) return { byId, unassigned: settledPurchases };

  // Aufsteigend (älteste Runde zuerst) → erste passende = zuständig
  const roundsAsc = [...rounds].sort((a, b) =>
    a.firstAt.localeCompare(b.firstAt)
  );

  for (const p of settledPurchases) {
    const t = getPurchaseEntryTime(p);
    if (!t) {
      unassigned.push(p);
      continue;
    }
    let placed = false;
    for (const r of roundsAsc) {
      if (t < r.maxAt) {
        byId[r.id].push(p);
        placed = true;
        break;
      }
    }
    if (!placed) unassigned.push(p);
  }
  return { byId, unassigned };
}

// ────────────────────────────────────────────────────────────────────
// FinanceView – Haushaltskasse
// ────────────────────────────────────────────────────────────────────
function FinanceView({
  purchases,
  settlements,
  members,
  plans,
  currentUserId,
  onAddPurchase,
  onEditPurchase,
  onViewPurchase,
  onDeletePurchase,
  onSettle,
  onDeleteSettlement,
  showToast,
}) {
  const [filter, setFilter] = useState("all"); // all | mine | month
  const [expandedSettle, setExpandedSettle] = useState(true);
  const [showSettledPurchases, setShowSettledPurchases] = useState(false);
  // IDs der Runden, die eingeklappt sind. Default: alle außer der neuesten
  const [collapsedRoundIds, setCollapsedRoundIds] = useState(() => new Set());
  const collapsedRoundIdsInitialized = useRef(false);
  const [confirmSettle, setConfirmSettle] = useState(null); // { from, to, amount_cents, count }
  const [lightboxPath, setLightboxPath] = useState(null);

  const balances = useMemo(
    () => computeBalances(purchases, settlements, members),
    [purchases, settlements, members]
  );
  const suggestions = useMemo(() => settleSuggestions(balances), [balances]);

  const myBalance = balances[currentUserId] ?? 0;
  const memberById = useMemo(
    () => Object.fromEntries(members.map((m) => [m.userId, m])),
    [members]
  );

  // Settlement-Cycle: was ist „abgeschlossen"?
  const settlementCutoff = useMemo(() => getLatestSettlementTime(settlements), [settlements]);
  const settlementRounds = useMemo(() => groupSettlementsByRound(settlements), [settlements]);
  const latestRound = settlementRounds[0] || null;

  // Initial: alle Runden außer der neuesten kollabieren
  useEffect(() => {
    if (collapsedRoundIdsInitialized.current) return;
    if (settlementRounds.length === 0) return;
    const collapsed = new Set(settlementRounds.slice(1).map((r) => r.id));
    setCollapsedRoundIds(collapsed);
    collapsedRoundIdsInitialized.current = true;
  }, [settlementRounds]);

  // Wenn neue Runden hinzukommen → diese sind by default expanded
  // (Nur neue runden werden expanded; bestehender state bleibt erhalten)
  useEffect(() => {
    if (!collapsedRoundIdsInitialized.current) return;
    setCollapsedRoundIds((prev) => {
      const known = new Set(settlementRounds.map((r) => r.id));
      const next = new Set();
      for (const id of prev) if (known.has(id)) next.add(id);
      return next;
    });
  }, [settlementRounds]);

  const toggleRoundCollapsed = (roundId) => {
    setCollapsedRoundIds((prev) => {
      const next = new Set(prev);
      if (next.has(roundId)) next.delete(roundId);
      else next.add(roundId);
      return next;
    });
  };

  const filteredPurchases = useMemo(() => {
    const now = new Date();
    return [...purchases]
      .filter((p) => {
        if (filter === "mine") return p.paid_by === currentUserId;
        if (filter === "month") {
          const d = new Date(p.purchased_at);
          return (
            d.getMonth() === now.getMonth() &&
            d.getFullYear() === now.getFullYear()
          );
        }
        return true;
      })
      .sort((a, b) => (b.purchased_at || "").localeCompare(a.purchased_at || ""));
  }, [purchases, filter, currentUserId]);

  // Aufteilung in offene (nach Cutoff) und abgerechnete (vor Cutoff)
  const { openPurchases, settledPurchases } = useMemo(() => {
    const open = [];
    const settled = [];
    for (const p of filteredPurchases) {
      if (isPurchaseSettled(p, settlementCutoff)) settled.push(p);
      else open.push(p);
    }
    return { openPurchases: open, settledPurchases: settled };
  }, [filteredPurchases, settlementCutoff]);

  // Pro Runde: welche Käufe gehören dazu?
  const purchasesByRound = useMemo(
    () => assignPurchasesToRounds(settledPurchases, settlementRounds),
    [settledPurchases, settlementRounds]
  );

  const monthTotal = useMemo(() => {
    const now = new Date();
    return purchases
      .filter((p) => {
        const d = new Date(p.purchased_at);
        return (
          d.getMonth() === now.getMonth() &&
          d.getFullYear() === now.getFullYear()
        );
      })
      .reduce((s, p) => s + (p.amount_cents || 0), 0);
  }, [purchases]);

  const totalAll = useMemo(
    () => purchases.reduce((s, p) => s + (p.amount_cents || 0), 0),
    [purchases]
  );

  const splitModeLabel = (mode) => {
    switch (mode) {
      case "payer_only":
        return "selbst getragen";
      case "shares":
        return "Anteile";
      case "exact":
        return "exakt";
      default:
        return null; // 'equal' = Standardfall, kein Label
    }
  };

  return (
    <div className="fade-in">
      {/* Header */}
      <div
        style={{
          display: "flex",
          alignItems: "flex-start",
          justifyContent: "space-between",
          flexWrap: "wrap",
          gap: "0.75rem",
          marginBottom: "1.25rem",
        }}
      >
        <div>
          <h2
            className="font-display"
            style={{
              fontSize: "1.75rem",
              fontWeight: 600,
              margin: "0 0 0.2rem",
              display: "flex",
              alignItems: "center",
              gap: "0.5rem",
            }}
          >
            <Wallet size={22} style={{ color: "#3d4a2a" }} /> Haushaltskasse
          </h2>
          <div style={{ color: "#6b6358" }}>
            Wer hat was bezahlt – und wer schuldet wem wie viel.
          </div>
        </div>
        <button className="btn-primary" onClick={onAddPurchase}>
          <Plus size={16} /> Einkauf erfassen
        </button>
      </div>

      {/* Übersichts-Karten — Hero-Saldo + kompakte Sekundärs */}
      <div
        style={{
          display: "grid",
          gridTemplateColumns: "minmax(0, 1.4fr) minmax(0, 1fr)",
          gap: "0.75rem",
          marginBottom: "1.25rem",
        }}
      >
        {/* HERO: Dein Saldo */}
        <div
          className="card"
          style={{
            padding: "1.1rem 1.25rem",
            background:
              myBalance > 1
                ? "linear-gradient(135deg, #fdfaf3 0%, rgba(61,74,42,0.06) 100%)"
                : myBalance < -1
                ? "linear-gradient(135deg, #fdfaf3 0%, rgba(168,66,42,0.07) 100%)"
                : "#fdfaf3",
            borderLeft: `4px solid ${
              myBalance > 1 ? "#3d4a2a" : myBalance < -1 ? "#a8422a" : "#bfb5a3"
            }`,
            display: "flex",
            flexDirection: "column",
            justifyContent: "center",
            minHeight: "7rem",
          }}
        >
          <div
            style={{
              fontSize: "0.72rem",
              color: "#8a8275",
              textTransform: "uppercase",
              letterSpacing: "0.08em",
              fontWeight: 600,
              marginBottom: "0.4rem",
              display: "flex",
              alignItems: "center",
              gap: "0.4rem",
            }}
          >
            {myBalance > 1 ? <TrendingUp size={12} /> : myBalance < -1 ? <ArrowRight size={12} /> : <CheckCircle2 size={12} />}
            Dein Saldo
          </div>
          <div
            className="font-display"
            style={{
              fontSize: "2.1rem",
              fontWeight: 600,
              lineHeight: 1.1,
              color:
                myBalance > 1
                  ? "#3d4a2a"
                  : myBalance < -1
                  ? "#a8422a"
                  : "#2a2620",
              fontVariantNumeric: "tabular-nums",
            }}
          >
            {myBalance > 0 ? "+" : ""}
            {fmtMoney(myBalance)}
          </div>
          <div
            style={{
              color: "#6b6358",
              fontSize: "0.85rem",
              marginTop: "0.3rem",
            }}
          >
            {myBalance > 1
              ? "bekommst du zurück"
              : myBalance < -1
              ? "schuldest du noch"
              : "alles ausgeglichen ✓"}
          </div>
        </div>

        {/* Sekundär: 2 kleine Karten gestapelt */}
        <div style={{ display: "grid", gap: "0.5rem", gridTemplateRows: "1fr 1fr" }}>
          <div
            className="card"
            style={{
              padding: "0.6rem 0.85rem",
              display: "flex",
              alignItems: "center",
              justifyContent: "space-between",
              gap: "0.5rem",
            }}
          >
            <div style={{ minWidth: 0 }}>
              <div
                style={{
                  fontSize: "0.68rem",
                  color: "#8a8275",
                  textTransform: "uppercase",
                  letterSpacing: "0.07em",
                  fontWeight: 600,
                  marginBottom: "0.1rem",
                }}
              >
                Diesen Monat
              </div>
              <div
                className="font-display"
                style={{
                  fontSize: "1.1rem",
                  fontWeight: 600,
                  fontVariantNumeric: "tabular-nums",
                  lineHeight: 1.1,
                }}
              >
                {fmtMoney(monthTotal)}
              </div>
            </div>
            <div
              style={{
                width: "2rem",
                height: "2rem",
                borderRadius: "8px",
                background: "rgba(74, 107, 138, 0.1)",
                color: "#4a6b8a",
                display: "flex",
                alignItems: "center",
                justifyContent: "center",
                flexShrink: 0,
              }}
            >
              <Calendar size={14} />
            </div>
          </div>

          <div
            className="card"
            style={{
              padding: "0.6rem 0.85rem",
              display: "flex",
              alignItems: "center",
              justifyContent: "space-between",
              gap: "0.5rem",
            }}
          >
            <div style={{ minWidth: 0 }}>
              <div
                style={{
                  fontSize: "0.68rem",
                  color: "#8a8275",
                  textTransform: "uppercase",
                  letterSpacing: "0.07em",
                  fontWeight: 600,
                  marginBottom: "0.1rem",
                }}
              >
                {purchases.length} {purchases.length === 1 ? "Einkauf" : "Einkäufe"}
              </div>
              <div
                className="font-display"
                style={{
                  fontSize: "1.1rem",
                  fontWeight: 600,
                  fontVariantNumeric: "tabular-nums",
                  lineHeight: 1.1,
                }}
              >
                {fmtMoney(totalAll)}
              </div>
            </div>
            <div
              style={{
                width: "2rem",
                height: "2rem",
                borderRadius: "8px",
                background: "rgba(192, 106, 67, 0.12)",
                color: "#c06a43",
                display: "flex",
                alignItems: "center",
                justifyContent: "center",
                flexShrink: 0,
              }}
            >
              <Receipt size={14} />
            </div>
          </div>
        </div>
      </div>

      {/* Salden aller Mitglieder — nur wenn Bewegung drin ist oder >2 Mitglieder */}
      {members.length > 1 && (() => {
        const allZero = members.every((m) => Math.abs(balances[m.userId] ?? 0) <= 1);
        if (allZero && members.length <= 2) {
          // Alles ausgeglichen → kompakter Hinweis statt großer Block
          return (
            <div
              style={{
                display: "flex",
                alignItems: "center",
                gap: "0.5rem",
                padding: "0.6rem 0.85rem",
                marginBottom: "1.25rem",
                background: "rgba(61, 74, 42, 0.06)",
                border: "1px solid rgba(61, 74, 42, 0.15)",
                borderRadius: "8px",
                fontSize: "0.88rem",
                color: "#3d4a2a",
              }}
            >
              <CheckCircle2 size={15} />
              <span>Alle Salden im Haushalt sind ausgeglichen.</span>
            </div>
          );
        }
        return (
          <div
            className="card"
            style={{ padding: "0.85rem 1rem", marginBottom: "1.25rem" }}
          >
            <div
              style={{
                display: "flex",
                alignItems: "center",
                gap: "0.45rem",
                marginBottom: "0.6rem",
              }}
            >
              <Users size={15} style={{ color: "#3d4a2a" }} />
              <h3
                className="font-display"
                style={{ margin: 0, fontSize: "1rem", fontWeight: 600 }}
              >
                Salden im Haushalt
              </h3>
              {allZero && (
                <span
                  style={{
                    marginLeft: "auto",
                    fontSize: "0.75rem",
                    color: "#3d4a2a",
                    display: "inline-flex",
                    alignItems: "center",
                    gap: "0.25rem",
                  }}
                >
                  <CheckCircle2 size={12} /> ausgeglichen
                </span>
              )}
            </div>
            <div style={{ display: "grid", gap: "0.3rem" }}>
              {members.map((m) => {
                const v = balances[m.userId] ?? 0;
                const isMe = m.userId === currentUserId;
                const sign = v > 1 ? "positive" : v < -1 ? "negative" : "neutral";
                return (
                  <div
                    key={m.userId}
                    style={{
                      display: "flex",
                      justifyContent: "space-between",
                      alignItems: "center",
                      padding: "0.45rem 0.6rem",
                      borderRadius: "6px",
                      background: isMe
                        ? "rgba(61, 74, 42, 0.06)"
                        : "transparent",
                    }}
                  >
                    <span
                      style={{
                        fontWeight: isMe ? 600 : 400,
                        display: "inline-flex",
                        alignItems: "center",
                        gap: "0.45rem",
                      }}
                    >
                      <Avatar name={memberDisplayName(m)} size={20} />
                      {memberDisplayName(m)}{" "}
                      {isMe && (
                        <span style={{ color: "#8a8275", fontWeight: 400, fontSize: "0.85rem" }}>
                          (du)
                        </span>
                      )}
                    </span>
                    <span
                      style={{
                        fontWeight: 600,
                        fontVariantNumeric: "tabular-nums",
                        color:
                          sign === "positive"
                            ? "#3d4a2a"
                            : sign === "negative"
                            ? "#a8422a"
                            : "#8a8275",
                      }}
                    >
                      {v > 0 ? "+" : ""}
                      {fmtMoney(v)}
                    </span>
                  </div>
                );
              })}
            </div>
          </div>
        );
      })()}

      {/* Ausgleichs-Vorschlag */}
      {suggestions.length > 0 && (
        <div
          className="card"
          style={{
            padding: "1rem 1.1rem",
            marginBottom: "1.25rem",
            borderLeft: "3px solid #c06a43",
          }}
        >
          <button
            onClick={() => setExpandedSettle((v) => !v)}
            style={{
              display: "flex",
              alignItems: "center",
              gap: "0.5rem",
              width: "100%",
              background: "none",
              border: "none",
              padding: 0,
              cursor: "pointer",
              fontFamily: "inherit",
              fontSize: "1.05rem",
              fontWeight: 600,
              color: "#2a2620",
            }}
            className="font-display"
          >
            <ArrowRightLeft size={16} style={{ color: "#c06a43" }} />
            Ausgleichs-Vorschlag ({suggestions.length}{" "}
            {suggestions.length === 1 ? "Zahlung" : "Zahlungen"})
            {expandedSettle ? (
              <ChevronUp size={16} style={{ marginLeft: "auto" }} />
            ) : (
              <ChevronDown size={16} style={{ marginLeft: "auto" }} />
            )}
          </button>

          {expandedSettle && (
            <div
              style={{ display: "grid", gap: "0.5rem", marginTop: "0.75rem" }}
            >
              {suggestions.map((s, i) => {
                const involvesMe =
                  s.from === currentUserId || s.to === currentUserId;
                return (
                  <div
                    key={i}
                    style={{
                      display: "flex",
                      alignItems: "center",
                      justifyContent: "space-between",
                      gap: "0.6rem",
                      padding: "0.6rem 0.75rem",
                      background: involvesMe
                        ? "rgba(192, 106, 67, 0.08)"
                        : "#f5efe4",
                      border: "1px solid #e4dccc",
                      borderRadius: "8px",
                      flexWrap: "wrap",
                    }}
                  >
                    <div
                      style={{
                        display: "flex",
                        alignItems: "center",
                        gap: "0.4rem",
                        flex: 1,
                        minWidth: 0,
                        flexWrap: "wrap",
                      }}
                    >
                      <strong>
                        {memberDisplayName(memberById[s.from])}
                      </strong>
                      <ArrowRight
                        size={14}
                        style={{ color: "#8a8275", flexShrink: 0 }}
                      />
                      <strong>
                        {memberDisplayName(memberById[s.to])}
                      </strong>
                      <span
                        style={{
                          marginLeft: "auto",
                          fontWeight: 600,
                          fontVariantNumeric: "tabular-nums",
                        }}
                      >
                        {fmtMoney(s.amount_cents)}
                      </span>
                    </div>
                    <button
                      className="btn-secondary"
                      style={{
                        padding: "0.35rem 0.75rem",
                        fontSize: "0.85rem",
                      }}
                      onClick={() => {
                        // Anzahl Käufe, die durch diese Abrechnung als
                        // „abgeschlossen" gelten werden — basierend auf dem
                        // Eintragungs-Zeitpunkt im System, nicht auf
                        // purchased_at (Bug-Fix).
                        const cutoffCandidate = new Date().toISOString();
                        const affectedCount = purchases.filter((p) => {
                          const t = getPurchaseEntryTime(p);
                          if (!t) return false;
                          if (t >= cutoffCandidate) return false;
                          if (settlementCutoff && t < settlementCutoff) return false;
                          return true;
                        }).length;
                        setConfirmSettle({
                          from: s.from,
                          to: s.to,
                          amount_cents: s.amount_cents,
                          count: affectedCount,
                        });
                      }}
                    >
                      <CheckCircle2 size={14} /> Bezahlt
                    </button>
                  </div>
                );
              })}
              <div
                style={{
                  fontSize: "0.78rem",
                  color: "#8a8275",
                  marginTop: "0.3rem",
                  lineHeight: 1.4,
                }}
              >
                Klicke „Bezahlt" sobald die Überweisung bzw. Bargeld-Übergabe
                erfolgt ist. Alle bisherigen Einkäufe werden danach als
                <strong style={{ color: "#3d4a2a" }}> abgerechnet</strong> markiert.
              </div>
            </div>
          )}
        </div>
      )}

      {/* Filter + Einkaufsliste */}
      <div
        style={{
          display: "flex",
          alignItems: "center",
          gap: "0.4rem",
          marginBottom: "0.75rem",
          flexWrap: "wrap",
        }}
      >
        <h3
          className="font-display"
          style={{
            margin: 0,
            fontSize: "1.1rem",
            fontWeight: 600,
            marginRight: "auto",
          }}
        >
          Einkäufe
        </h3>
        {[
          { id: "all", label: "Alle" },
          { id: "mine", label: "Meine" },
          { id: "month", label: "Monat" },
        ].map((f) => (
          <button
            key={f.id}
            onClick={() => setFilter(f.id)}
            className={filter === f.id ? "btn-primary" : "btn-ghost"}
            style={{ padding: "0.35rem 0.7rem", fontSize: "0.85rem" }}
          >
            {f.label}
          </button>
        ))}
      </div>

      {filteredPurchases.length === 0 ? (
        <div
          className="card"
          style={{
            padding: "2rem",
            textAlign: "center",
            borderStyle: "dashed",
          }}
        >
          <Receipt
            size={28}
            style={{ color: "#c06a43", margin: "0 auto 0.5rem" }}
          />
          <div style={{ fontWeight: 600, marginBottom: "0.25rem" }}>
            {filter === "all" ? "Noch keine Einkäufe" : "Keine Einkäufe in diesem Filter"}
          </div>
          <div style={{ color: "#6b6358", fontSize: "0.9rem" }}>
            {filter === "all"
              ? "Erfasse deinen ersten Einkauf über den Button oben rechts."
              : "Wechsle den Filter oder erfasse einen neuen Einkauf."}
          </div>
        </div>
      ) : (
        <>
          {/* Offene Periode */}
          {openPurchases.length > 0 && (
            <div style={{ display: "grid", gap: "0.5rem" }}>
              {openPurchases.map((p) => (
                <PurchaseRow
                  key={p.id}
                  purchase={p}
                  payer={memberById[p.paid_by]}
                  isMine={p.paid_by === currentUserId}
                  modeLabel={splitModeLabel(p.split_mode)}
                  settled={false}
                  onEdit={onEditPurchase}
                  onDelete={onDeletePurchase}
                  onShowReceipt={(path) => setLightboxPath(path)}
                />
              ))}
            </div>
          )}

          {/* Section-Divider + abgerechnete Periode */}
          {settledPurchases.length > 0 && (
            <div style={{ marginTop: openPurchases.length > 0 ? "1.25rem" : 0 }}>
              <button
                onClick={() => setShowSettledPurchases((v) => !v)}
                style={{
                  width: "100%",
                  display: "flex",
                  alignItems: "center",
                  gap: "0.5rem",
                  padding: "0.6rem 0.85rem",
                  background: "rgba(61, 74, 42, 0.05)",
                  border: "1px solid rgba(61, 74, 42, 0.18)",
                  borderRadius: "8px",
                  fontFamily: "inherit",
                  fontSize: "0.88rem",
                  fontWeight: 500,
                  color: "#3d4a2a",
                  cursor: "pointer",
                  marginBottom: showSettledPurchases ? "0.5rem" : 0,
                }}
                aria-expanded={showSettledPurchases}
              >
                <CheckCircle2 size={14} />
                <span>
                  Abgerechnet
                  {latestRound && (
                    <span style={{ color: "#8a8275", fontWeight: 400 }}>
                      {" "}
                      · zuletzt am {fmtPurchaseDate(latestRound.day)}
                    </span>
                  )}
                </span>
                <span
                  style={{
                    background: "#3d4a2a",
                    color: "#f5efe4",
                    fontSize: "0.72rem",
                    padding: "1px 7px",
                    borderRadius: "999px",
                    fontWeight: 600,
                  }}
                >
                  {settledPurchases.length}
                </span>
                {showSettledPurchases ? (
                  <ChevronUp size={14} style={{ marginLeft: "auto" }} />
                ) : (
                  <ChevronDown size={14} style={{ marginLeft: "auto" }} />
                )}
              </button>

              {showSettledPurchases && (
                <div style={{ display: "grid", gap: "0.6rem" }}>
                  {settlementRounds.map((round) => {
                    const purchasesInRound = purchasesByRound.byId[round.id] || [];
                    if (purchasesInRound.length === 0) return null;
                    const roundLabel = formatRoundLabel(round, settlementRounds);
                    const isCollapsed = collapsedRoundIds.has(round.id);
                    const purchasesSum = purchasesInRound.reduce(
                      (sum, p) => sum + (p.amount_cents || 0),
                      0
                    );
                    return (
                      <div
                        key={round.id}
                        className="card"
                        style={{
                          padding: 0,
                          overflow: "hidden",
                        }}
                      >
                        {/* Round-Header — klickbar zum Ein/Ausklappen */}
                        <button
                          onClick={() => toggleRoundCollapsed(round.id)}
                          aria-expanded={!isCollapsed}
                          style={{
                            display: "flex",
                            flexWrap: "wrap",
                            alignItems: "center",
                            gap: "0.4rem",
                            width: "100%",
                            padding: "0.6rem 0.85rem",
                            background: "#f8f3e8",
                            borderBottom: isCollapsed ? "none" : "1px solid #e4dccc",
                            border: "none",
                            borderTopLeftRadius: "10px",
                            borderTopRightRadius: "10px",
                            borderBottomLeftRadius: isCollapsed ? "10px" : 0,
                            borderBottomRightRadius: isCollapsed ? "10px" : 0,
                            fontSize: "0.8rem",
                            color: "#6b6358",
                            fontFamily: "inherit",
                            cursor: "pointer",
                            textAlign: "left",
                          }}
                        >
                          <span
                            style={{
                              fontWeight: 600,
                              color: "#3d4a2a",
                              display: "inline-flex",
                              alignItems: "center",
                              gap: "0.3rem",
                            }}
                          >
                            <CheckCircle2 size={12} />
                            Abrechnung {roundLabel}
                          </span>
                          <span style={{ color: "#c4b9a5" }}>·</span>
                          <span style={{ color: "#8a8275", fontWeight: 500 }}>
                            {purchasesInRound.length}{" "}
                            {purchasesInRound.length === 1 ? "Einkauf" : "Einkäufe"}
                          </span>
                          <span style={{ color: "#c4b9a5" }}>·</span>
                          {round.items.map((s, i) => (
                            <span
                              key={s.id}
                              style={{
                                display: "inline-flex",
                                alignItems: "center",
                                gap: "0.25rem",
                              }}
                            >
                              <strong>
                                {memberDisplayName(memberById[s.from_user])}
                              </strong>
                              <ArrowRight size={11} style={{ color: "#8a8275" }} />
                              <strong>
                                {memberDisplayName(memberById[s.to_user])}
                              </strong>
                              <span style={{ fontVariantNumeric: "tabular-nums" }}>
                                {fmtMoney(s.amount_cents)}
                              </span>
                              <span
                                role="button"
                                tabIndex={0}
                                onClick={(e) => {
                                  e.stopPropagation();
                                  onDeleteSettlement(s.id);
                                }}
                                onKeyDown={(e) => {
                                  if (e.key === "Enter" || e.key === " ") {
                                    e.preventDefault();
                                    e.stopPropagation();
                                    onDeleteSettlement(s.id);
                                  }
                                }}
                                title="Diese Ausgleichszahlung rückgängig"
                                style={{
                                  color: "#bfb5a3",
                                  cursor: "pointer",
                                  padding: "0 2px",
                                  marginLeft: "0.15rem",
                                  display: "inline-flex",
                                  alignItems: "center",
                                  borderRadius: "3px",
                                }}
                              >
                                <X size={12} />
                              </span>
                              {i < round.items.length - 1 && (
                                <span style={{ color: "#c4b9a5", marginLeft: "0.25rem" }}>·</span>
                              )}
                            </span>
                          ))}
                          <span
                            style={{
                              marginLeft: "auto",
                              color: "#8a8275",
                              display: "inline-flex",
                              alignItems: "center",
                            }}
                          >
                            {isCollapsed ? <ChevronDown size={14} /> : <ChevronUp size={14} />}
                          </span>
                        </button>

                        {/* Käufe dieser Runde — kompakt */}
                        {!isCollapsed && (
                          <>
                            {purchasesInRound.map((p) => (
                              <SettledPurchaseCompactRow
                                key={p.id}
                                purchase={p}
                                payer={memberById[p.paid_by]}
                                modeLabel={splitModeLabel(p.split_mode)}
                                onView={onViewPurchase}
                                onShowReceipt={(path) => setLightboxPath(path)}
                              />
                            ))}
                            {/* Summen-Footer */}
                            <div
                              style={{
                                padding: "0.45rem 0.85rem",
                                background: "#fbf7ec",
                                borderTop: "1px solid #efe7d4",
                                display: "flex",
                                justifyContent: "space-between",
                                alignItems: "center",
                                fontSize: "0.78rem",
                                color: "#6b6358",
                              }}
                            >
                              <span>Summe dieser Runde</span>
                              <span
                                style={{
                                  fontWeight: 600,
                                  color: "#4a443b",
                                  fontVariantNumeric: "tabular-nums",
                                }}
                              >
                                {fmtMoney(purchasesSum)}
                              </span>
                            </div>
                          </>
                        )}
                      </div>
                    );
                  })}

                  {/* Käufe ohne klare Runden-Zuordnung (Fallback / Migrationsdaten) */}
                  {purchasesByRound.unassigned.length > 0 && (
                    <div
                      className="card"
                      style={{
                        padding: 0,
                        overflow: "hidden",
                        borderStyle: "dashed",
                      }}
                    >
                      <div
                        style={{
                          padding: "0.5rem 0.85rem",
                          background: "#f5efe4",
                          borderBottom: "1px dashed #d6cdb8",
                          fontSize: "0.78rem",
                          color: "#8a8275",
                          fontStyle: "italic",
                        }}
                      >
                        Ohne Runden-Zuordnung
                      </div>
                      {purchasesByRound.unassigned.map((p) => (
                        <SettledPurchaseCompactRow
                          key={p.id}
                          purchase={p}
                          payer={memberById[p.paid_by]}
                          modeLabel={splitModeLabel(p.split_mode)}
                          onView={onViewPurchase}
                          onShowReceipt={(path) => setLightboxPath(path)}
                        />
                      ))}
                    </div>
                  )}
                </div>
              )}
            </div>
          )}
        </>
      )}

      {/* Confirm-Dialog: vor Settlement zeigen, was abgeschlossen wird */}
      {confirmSettle && (
        <SettleConfirmDialog
          confirm={confirmSettle}
          fromName={memberDisplayName(memberById[confirmSettle.from])}
          toName={memberDisplayName(memberById[confirmSettle.to])}
          onCancel={() => setConfirmSettle(null)}
          onConfirm={async () => {
            const c = confirmSettle;
            setConfirmSettle(null);
            await onSettle({
              from_user: c.from,
              to_user: c.to,
              amount_cents: c.amount_cents,
            });
            const msg =
              c.count > 0
                ? `${fmtMoney(c.amount_cents)} ausgeglichen · ${c.count} ${
                    c.count === 1 ? "Einkauf" : "Einkäufe"
                  } abgerechnet`
                : `${fmtMoney(c.amount_cents)} als ausgeglichen markiert`;
            showToast(msg);
          }}
        />
      )}

      {lightboxPath && (
        <ReceiptLightbox
          path={lightboxPath}
          onClose={() => setLightboxPath(null)}
        />
      )}
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// SettledPurchaseCompactRow — kompakte Single-Line-Variante für
// abgerechnete Käufe innerhalb einer Runden-Karte. Kein Card-Chrome,
// nur eine Tabellen-Zeile mit Hairline-Divider zur nächsten.
// ────────────────────────────────────────────────────────────────────
function SettledPurchaseCompactRow({
  purchase: p,
  payer,
  modeLabel,
  onView,
  onShowReceipt,
}) {
  // Datum kompakt: "28.04."
  const compactDate = (() => {
    const d = p.purchased_at ? new Date(p.purchased_at) : null;
    if (!d) return "";
    return `${String(d.getDate()).padStart(2, "0")}.${String(d.getMonth() + 1).padStart(2, "0")}.`;
  })();

  return (
    <div
      role="button"
      tabIndex={0}
      onClick={() => onView && onView(p)}
      onKeyDown={(e) => {
        if (e.key === "Enter" || e.key === " ") {
          e.preventDefault();
          onView && onView(p);
        }
      }}
      style={{
        display: "grid",
        gridTemplateColumns: "5rem 1fr auto auto",
        gap: "0.7rem",
        alignItems: "center",
        padding: "0.55rem 0.85rem",
        cursor: "pointer",
        transition: "background 0.1s",
        borderTop: "1px solid #efe7d4",
      }}
      onMouseEnter={(e) => (e.currentTarget.style.background = "rgba(61, 74, 42, 0.025)")}
      onMouseLeave={(e) => (e.currentTarget.style.background = "transparent")}
      title="Details ansehen"
    >
      {/* Betrag */}
      <span
        style={{
          fontWeight: 600,
          fontVariantNumeric: "tabular-nums",
          color: "#4a443b",
          fontSize: "0.92rem",
        }}
      >
        {fmtMoney(p.amount_cents, p.currency)}
      </span>
      {/* Body */}
      <div style={{ minWidth: 0 }}>
        <div
          style={{
            color: "#2a2620",
            fontSize: "0.88rem",
            overflow: "hidden",
            textOverflow: "ellipsis",
            whiteSpace: "nowrap",
          }}
        >
          {p.store ? p.store : <span style={{ color: "#bfb5a3" }}>—</span>}
          {p.note && (
            <span style={{ color: "#8a8275", fontWeight: 400 }}> · {p.note}</span>
          )}
        </div>
        <div
          style={{
            color: "#8a8275",
            fontSize: "0.72rem",
            marginTop: "1px",
            display: "inline-flex",
            gap: "0.35rem",
            alignItems: "center",
            flexWrap: "wrap",
          }}
        >
          <span>{memberDisplayName(payer)}</span>
          <span>·</span>
          <span>{compactDate}</span>
          {modeLabel && (
            <>
              <span>·</span>
              <span style={{ fontStyle: "italic" }}>{modeLabel}</span>
            </>
          )}
        </div>
      </div>
      {/* Quittung */}
      {p.receipt_path ? (
        <ReceiptThumbnail
          path={p.receipt_path}
          size={34}
          onClick={() => onShowReceipt(p.receipt_path)}
        />
      ) : (
        <span style={{ width: "34px", height: "34px", display: "inline-block" }} />
      )}
      {/* Eye */}
      <span
        aria-label="Details ansehen"
        title="Details ansehen (abgerechnet · nicht bearbeitbar)"
        style={{
          width: "1.7rem",
          height: "1.7rem",
          display: "inline-flex",
          alignItems: "center",
          justifyContent: "center",
          color: "#8a8275",
          borderRadius: "4px",
        }}
      >
        <Eye size={14} />
      </span>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// PurchaseRow — eine Einkaufs-Zeile (offen oder abgerechnet)
// ────────────────────────────────────────────────────────────────────
function PurchaseRow({
  purchase: p,
  payer,
  isMine,
  modeLabel,
  settled,
  onEdit,
  onDelete,
  onShowReceipt,
  onView,
}) {
  return (
    <div
      className="card"
      style={{
        padding: "0.7rem 0.95rem",
        display: "flex",
        alignItems: "center",
        gap: "0.7rem",
        borderLeft: settled
          ? "3px solid rgba(61, 74, 42, 0.35)"
          : isMine
          ? "3px solid #3d4a2a"
          : "1.5px solid #e4dccc",
        opacity: settled ? 0.72 : 1,
        background: settled ? "rgba(61, 74, 42, 0.025)" : "#fdfaf3",
      }}
    >
      <div
        style={{
          width: "2.2rem",
          height: "2.2rem",
          borderRadius: "8px",
          background: settled ? "rgba(61, 74, 42, 0.15)" : "rgba(61, 74, 42, 0.1)",
          color: settled ? "rgba(61, 74, 42, 0.7)" : "#3d4a2a",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          flexShrink: 0,
        }}
      >
        {settled ? <CheckCircle2 size={15} /> : <ShoppingCart size={15} />}
      </div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div
          style={{
            display: "flex",
            gap: "0.5rem",
            alignItems: "baseline",
            flexWrap: "wrap",
          }}
        >
          <strong
            style={{
              fontVariantNumeric: "tabular-nums",
              color: settled ? "#6b6358" : "#2a2620",
            }}
          >
            {fmtMoney(p.amount_cents, p.currency)}
          </strong>
          {p.store && (
            <span style={{ color: "#6b6358", fontSize: "0.88rem" }}>
              · {p.store}
            </span>
          )}
        </div>
        <div
          style={{
            color: "#6b6358",
            fontSize: "0.8rem",
            display: "flex",
            gap: "0.35rem",
            flexWrap: "wrap",
          }}
        >
          <span>{memberDisplayName(payer)}</span>
          <span>·</span>
          <span>{fmtPurchaseDate(p.purchased_at)}</span>
          {modeLabel && (
            <>
              <span>·</span>
              <span style={{ fontStyle: "italic" }}>{modeLabel}</span>
            </>
          )}
          {p.note && (
            <>
              <span>·</span>
              <span
                style={{
                  overflow: "hidden",
                  textOverflow: "ellipsis",
                  whiteSpace: "nowrap",
                  maxWidth: "16rem",
                }}
                title={p.note}
              >
                {p.note}
              </span>
            </>
          )}
        </div>
      </div>
      {p.receipt_path && (
        <ReceiptThumbnail
          path={p.receipt_path}
          size={34}
          onClick={() => onShowReceipt(p.receipt_path)}
        />
      )}
      {settled ? (
        <button
          className="icon-btn"
          onClick={() => onView && onView(p)}
          aria-label="Details ansehen"
          title="Details ansehen (abgerechnet · nicht bearbeitbar)"
          disabled={!onView}
          style={{ color: "#6b6358" }}
        >
          <Eye size={14} />
        </button>
      ) : (
        <>
          <button
            className="icon-btn"
            onClick={() => onEdit(p)}
            aria-label="Bearbeiten"
            title="Bearbeiten"
          >
            <Pencil size={13} />
          </button>
          <button
            className="icon-btn btn-danger"
            onClick={() => onDelete(p.id)}
            aria-label="Löschen"
            title="Löschen"
          >
            <Trash2 size={13} />
          </button>
        </>
      )}
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// SettleConfirmDialog — zeigt vor Abrechnung, was abgeschlossen wird
// ────────────────────────────────────────────────────────────────────
function SettleConfirmDialog({ confirm, fromName, toName, onCancel, onConfirm }) {
  return (
    <div
      role="dialog"
      aria-modal="true"
      aria-labelledby="settle-confirm-title"
      onClick={onCancel}
      style={{
        position: "fixed",
        inset: 0,
        background: "rgba(0,0,0,0.55)",
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        zIndex: 200,
        padding: "1rem",
      }}
    >
      <div
        className="scale-in"
        onClick={(e) => e.stopPropagation()}
        style={{
          background: "#fdfaf3",
          borderRadius: "12px",
          padding: "1.4rem 1.5rem",
          maxWidth: "26rem",
          width: "100%",
          boxShadow: "0 16px 40px rgba(0,0,0,0.3)",
        }}
      >
        <h3
          id="settle-confirm-title"
          className="font-display"
          style={{
            margin: "0 0 0.5rem",
            fontSize: "1.15rem",
            fontWeight: 600,
            display: "flex",
            alignItems: "center",
            gap: "0.5rem",
          }}
        >
          <CheckCircle2 size={18} style={{ color: "#3d4a2a" }} /> Zahlung bestätigen
        </h3>
        <div
          style={{
            display: "flex",
            alignItems: "center",
            gap: "0.5rem",
            padding: "0.7rem 0.85rem",
            background: "rgba(61, 74, 42, 0.07)",
            borderRadius: "8px",
            marginBottom: "0.85rem",
            flexWrap: "wrap",
          }}
        >
          <strong>{fromName}</strong>
          <ArrowRight size={14} style={{ color: "#8a8275" }} />
          <strong>{toName}</strong>
          <span
            style={{
              marginLeft: "auto",
              fontWeight: 600,
              fontVariantNumeric: "tabular-nums",
              fontSize: "1.05rem",
            }}
          >
            {fmtMoney(confirm.amount_cents)}
          </span>
        </div>
        <p style={{ margin: "0 0 0.4rem", fontSize: "0.92rem", color: "#4a443b", lineHeight: 1.5 }}>
          Wenn die Überweisung bzw. Bargeld-Übergabe erfolgt ist, wird der Saldo
          ausgeglichen.
        </p>
        {confirm.count > 0 && (
          <div
            style={{
              padding: "0.6rem 0.8rem",
              background: "rgba(74, 107, 138, 0.07)",
              borderLeft: "3px solid #4a6b8a",
              borderRadius: "0 6px 6px 0",
              fontSize: "0.85rem",
              color: "#4a443b",
              marginBottom: "1rem",
              lineHeight: 1.45,
            }}
          >
            <strong>{confirm.count}</strong>{" "}
            {confirm.count === 1 ? "Einkauf wird" : "Einkäufe werden"} dadurch als
            <strong> abgerechnet</strong> markiert und in den Bereich
            „Abgerechnet" verschoben.
          </div>
        )}
        <div
          style={{
            display: "flex",
            gap: "0.5rem",
            justifyContent: "flex-end",
            marginTop: "0.5rem",
          }}
        >
          <button className="btn-ghost" onClick={onCancel}>
            Abbrechen
          </button>
          <button className="btn-primary" onClick={onConfirm}>
            <CheckCircle2 size={14} /> Ja, bezahlt
          </button>
        </div>
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Bild-Komprimierung (Canvas-basiert, läuft im Browser)
// Reduziert Foto-Größe vor dem Upload — spart Storage + Bandbreite
// ────────────────────────────────────────────────────────────────────
async function compressImage(file, opts = {}) {
  const maxDim = opts.maxDim || 1920;
  const quality = opts.quality || 0.85;
  // Kein Bild oder bereits klein? → unverändert zurückgeben
  if (!file || !file.type.startsWith("image/")) return file;

  try {
    const dataUrl = await new Promise((resolve, reject) => {
      const r = new FileReader();
      r.onload = () => resolve(r.result);
      r.onerror = reject;
      r.readAsDataURL(file);
    });

    const img = await new Promise((resolve, reject) => {
      const im = new Image();
      im.onload = () => resolve(im);
      im.onerror = reject;
      im.src = dataUrl;
    });

    const ratio = Math.min(1, maxDim / Math.max(img.width, img.height));
    if (ratio === 1 && file.size < 500 * 1024) {
      // Schon klein, lohnt sich nicht
      return file;
    }
    const w = Math.round(img.width * ratio);
    const h = Math.round(img.height * ratio);

    const canvas = document.createElement("canvas");
    canvas.width = w;
    canvas.height = h;
    const ctx = canvas.getContext("2d");
    ctx.drawImage(img, 0, 0, w, h);

    const blob = await new Promise((resolve) =>
      canvas.toBlob(resolve, "image/jpeg", quality)
    );
    if (!blob) return file;

    return new File([blob], (file.name || "receipt").replace(/\.[^.]+$/, ".jpg"), {
      type: "image/jpeg",
      lastModified: Date.now(),
    });
  } catch (e) {
    console.warn("Bild-Komprimierung fehlgeschlagen, nutze Original:", e);
    return file;
  }
}

// ────────────────────────────────────────────────────────────────────
// ReceiptLightbox – Quittung im Vollbild anzeigen
// ────────────────────────────────────────────────────────────────────
function ReceiptLightbox({ path, onClose }) {
  const [url, setUrl] = useState(null);
  const [error, setError] = useState("");

  useEffect(() => {
    let active = true;
    setUrl(null);
    setError("");
    if (!path) return;
    db.getReceiptSignedUrl(path, 3600)
      .then((u) => {
        if (active) setUrl(u);
      })
      .catch((e) => {
        console.error(e);
        if (active) setError("Quittung konnte nicht geladen werden.");
      });
    return () => {
      active = false;
    };
  }, [path]);

  if (!path) return null;

  return (
    <div
      onClick={onClose}
      style={{
        position: "fixed",
        inset: 0,
        background: "rgba(0,0,0,0.85)",
        zIndex: 300,
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        padding: "1rem",
      }}
    >
      <button
        onClick={onClose}
        aria-label="Schließen"
        style={{
          position: "absolute",
          top: "1rem",
          right: "1rem",
          background: "rgba(255,255,255,0.15)",
          color: "#fff",
          border: "none",
          borderRadius: "50%",
          width: "2.5rem",
          height: "2.5rem",
          cursor: "pointer",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
        }}
      >
        <X size={20} />
      </button>
      {url && (
        <a
          href={url}
          download
          target="_blank"
          rel="noopener noreferrer"
          onClick={(e) => e.stopPropagation()}
          aria-label="Herunterladen"
          style={{
            position: "absolute",
            top: "1rem",
            right: "4rem",
            background: "rgba(255,255,255,0.15)",
            color: "#fff",
            borderRadius: "50%",
            width: "2.5rem",
            height: "2.5rem",
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            textDecoration: "none",
          }}
        >
          <Download size={18} />
        </a>
      )}
      <div
        onClick={(e) => e.stopPropagation()}
        style={{
          maxWidth: "95vw",
          maxHeight: "90vh",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
        }}
      >
        {error ? (
          <div style={{ color: "#fff", textAlign: "center" }}>
            <AlertCircle size={32} style={{ marginBottom: "0.5rem" }} />
            <div>{error}</div>
          </div>
        ) : !url ? (
          <div style={{ color: "#fff" }}>Lade Quittung…</div>
        ) : (
          <img
            src={url}
            alt="Quittung"
            style={{
              maxWidth: "95vw",
              maxHeight: "90vh",
              objectFit: "contain",
              borderRadius: "8px",
              boxShadow: "0 20px 60px rgba(0,0,0,0.4)",
            }}
          />
        )}
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// ReceiptThumbnail – kleines Vorschaubild mit Klick-zu-Lightbox
// ────────────────────────────────────────────────────────────────────
function ReceiptThumbnail({ path, size = 40, onClick }) {
  const [url, setUrl] = useState(null);
  useEffect(() => {
    let active = true;
    if (!path) return;
    db.getReceiptSignedUrl(path, 3600)
      .then((u) => active && setUrl(u))
      .catch(() => active && setUrl(null));
    return () => {
      active = false;
    };
  }, [path]);

  if (!path) return null;

  return (
    <button
      onClick={onClick}
      title="Quittung anzeigen"
      style={{
        width: size,
        height: size,
        border: "1.5px solid #e4dccc",
        borderRadius: "6px",
        overflow: "hidden",
        background: "#f5efe4",
        cursor: "pointer",
        padding: 0,
        flexShrink: 0,
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
      }}
    >
      {url ? (
        <img
          src={url}
          alt="Quittung"
          style={{ width: "100%", height: "100%", objectFit: "cover" }}
        />
      ) : (
        <Receipt size={16} style={{ color: "#bfb5a3" }} />
      )}
    </button>
  );
}

// ────────────────────────────────────────────────────────────────────
// PurchaseModal – Einkauf erfassen / bearbeiten
// ────────────────────────────────────────────────────────────────────
function PurchaseModal({
  purchase,
  members,
  currentUserId,
  plans,
  workspaceId,
  onCancel,
  onSave,
  readonly = false,
}) {
  const isEdit = !!purchase?.id;

  const initialAmount =
    purchase?.amount_cents != null
      ? (purchase.amount_cents / 100).toFixed(2).replace(".", ",")
      : "";

  const [amountStr, setAmountStr] = useState(initialAmount);
  const [paidBy, setPaidBy] = useState(purchase?.paid_by || currentUserId || "");
  const [purchasedAt, setPurchasedAt] = useState(() => {
    const d = purchase?.purchased_at
      ? new Date(purchase.purchased_at)
      : new Date();
    // YYYY-MM-DD für <input type="date">
    const pad = (n) => String(n).padStart(2, "0");
    return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
  });
  const [store, setStore] = useState(purchase?.store || "");
  const [note, setNote] = useState(purchase?.note || "");
  const [splitMode, setSplitMode] = useState(purchase?.split_mode || "equal");
  const [excludedMembers, setExcludedMembers] = useState(
    new Set(purchase?.excluded_members || [])
  );
  const [splitData, setSplitData] = useState(() => {
    const sd = purchase?.split_data || {};
    return Object.fromEntries(members.map((m) => [m.userId, sd[m.userId] ?? ""]));
  });
  const [weekStarts, setWeekStarts] = useState(
    new Set(purchase?.week_starts || [])
  );
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState("");

  // ── Quittungs-Foto ──
  // existingReceiptPath: bestehende Quittung (bei Edit), wird ggf. ersetzt/gelöscht
  // pendingFile: neu ausgewählte Datei, die beim Speichern hochgeladen wird
  // localPreviewUrl: ObjectURL für die Vorschau einer pendingFile
  const [existingReceiptPath, setExistingReceiptPath] = useState(
    purchase?.receipt_path || null
  );
  const [existingReceiptUrl, setExistingReceiptUrl] = useState(null);
  const [pendingFile, setPendingFile] = useState(null);
  const [localPreviewUrl, setLocalPreviewUrl] = useState(null);
  const [showLightbox, setShowLightbox] = useState(false);
  const [uploadingReceipt, setUploadingReceipt] = useState(false);
  const fileInputRef = useRef(null);

  // Bestehende Quittung als Signed URL für Vorschau laden
  useEffect(() => {
    let active = true;
    if (!existingReceiptPath || pendingFile) {
      setExistingReceiptUrl(null);
      return;
    }
    db.getReceiptSignedUrl(existingReceiptPath, 3600)
      .then((u) => active && setExistingReceiptUrl(u))
      .catch(() => active && setExistingReceiptUrl(null));
    return () => {
      active = false;
    };
  }, [existingReceiptPath, pendingFile]);

  // ObjectURL für lokale Vorschau einer noch nicht hochgeladenen Datei
  useEffect(() => {
    if (!pendingFile) {
      setLocalPreviewUrl(null);
      return;
    }
    const url = URL.createObjectURL(pendingFile);
    setLocalPreviewUrl(url);
    return () => URL.revokeObjectURL(url);
  }, [pendingFile]);

  const handleFilePick = async (e) => {
    const file = e.target.files?.[0];
    if (!file) return;
    if (!file.type.startsWith("image/")) {
      setError("Nur Bilddateien sind erlaubt.");
      return;
    }
    // Limit 15 MB roh (vor Komprimierung)
    if (file.size > 15 * 1024 * 1024) {
      setError("Bild zu groß (max. 15 MB).");
      return;
    }
    setError("");
    const compressed = await compressImage(file, { maxDim: 1920, quality: 0.85 });
    setPendingFile(compressed);
  };

  const removeReceipt = () => {
    setPendingFile(null);
    setExistingReceiptPath(null);
    if (fileInputRef.current) fileInputRef.current.value = "";
  };

  // Beträge in cent (Schweizer/DE Komma + Punkt akzeptiert)
  const parseAmountCents = (s) => {
    if (!s) return 0;
    const cleaned = s.replace(/\s/g, "").replace(",", ".");
    const n = Number(cleaned);
    if (!isFinite(n) || n < 0) return NaN;
    return Math.round(n * 100);
  };

  const amountCents = parseAmountCents(amountStr);
  const isAmountValid = !isNaN(amountCents) && amountCents > 0;

  // Verfügbare Wochen (geplante)
  const availableWeeks = useMemo(() => {
    if (!plans) return [];
    return Object.entries(plans)
      .filter(([, p]) => Object.values(p?.days || {}).some((d) => d?.recipeId))
      .map(([k]) => k)
      .sort((a, b) => b.localeCompare(a))
      .slice(0, 8);
  }, [plans]);

  const toggleExcluded = (uid) => {
    setExcludedMembers((prev) => {
      const n = new Set(prev);
      if (n.has(uid)) n.delete(uid);
      else n.add(uid);
      return n;
    });
  };

  const toggleWeek = (k) => {
    setWeekStarts((prev) => {
      const n = new Set(prev);
      if (n.has(k)) n.delete(k);
      else n.add(k);
      return n;
    });
  };

  const handleSave = async () => {
    setError("");
    if (!isAmountValid) {
      setError("Bitte einen gültigen Betrag eingeben.");
      return;
    }
    if (!paidBy) {
      setError("Bitte Bezahler wählen.");
      return;
    }

    let finalSplitData = {};
    if (splitMode === "shares") {
      for (const [uid, v] of Object.entries(splitData)) {
        if (excludedMembers.has(uid)) continue;
        const n = Number(String(v).replace(",", "."));
        if (isFinite(n) && n > 0) finalSplitData[uid] = n;
      }
      if (Object.keys(finalSplitData).length === 0) {
        setError("Bitte mindestens eine Anteils-Zahl > 0 vergeben.");
        return;
      }
    } else if (splitMode === "exact") {
      let sum = 0;
      for (const [uid, v] of Object.entries(splitData)) {
        if (excludedMembers.has(uid)) continue;
        const cents = parseAmountCents(String(v));
        if (isNaN(cents) || cents < 0) continue;
        if (cents > 0) finalSplitData[uid] = cents;
        sum += cents;
      }
      if (Math.abs(sum - amountCents) > 1) {
        setError(
          `Exakte Beträge müssen ${fmtMoney(amountCents)} ergeben (aktuell ${fmtMoney(sum)}).`
        );
        return;
      }
    }

    const isoDate = new Date(purchasedAt + "T12:00:00").toISOString();

    setSaving(true);
    try {
      // ── Quittungs-Foto-Handling ─────────────────────────────────
      // 1. Wenn Original ersetzt oder gelöscht: alte Datei aus Storage entfernen
      // 2. Wenn neue Datei: hochladen und Pfad merken
      const originalPath = purchase?.receipt_path || null;
      let finalReceiptPath = existingReceiptPath; // bleibt erhalten wenn nichts geändert

      if (pendingFile && workspaceId) {
        setUploadingReceipt(true);
        try {
          finalReceiptPath = await db.uploadReceipt(pendingFile, workspaceId);
        } finally {
          setUploadingReceipt(false);
        }
      }

      // Alte Datei löschen, wenn ersetzt oder bewusst entfernt
      if (
        originalPath &&
        originalPath !== finalReceiptPath
      ) {
        try {
          await db.deleteReceipt(originalPath);
        } catch (e) {
          console.warn("Alte Quittung konnte nicht gelöscht werden:", e);
        }
      }

      await onSave({
        ...(purchase?.id ? { id: purchase.id } : {}),
        amount_cents: amountCents,
        currency: "EUR",
        paid_by: paidBy,
        purchased_at: isoDate,
        store: store.trim() || null,
        note: note.trim() || null,
        split_mode: splitMode,
        split_data: finalSplitData,
        excluded_members: [...excludedMembers],
        week_starts: [...weekStarts],
        receipt_path: finalReceiptPath,
      });
    } catch (e) {
      console.error(e);
      setError("Speichern fehlgeschlagen. Bitte erneut versuchen.");
    } finally {
      setSaving(false);
    }
  };

  return (
    <div
      onClick={onCancel}
      style={{
        position: "fixed",
        inset: 0,
        background: "rgba(0,0,0,0.45)",
        zIndex: 200,
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        padding: "1rem",
      }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        className="scale-in"
        style={{
          background: "#fdfaf3",
          borderRadius: "14px",
          width: "100%",
          maxWidth: "32rem",
          maxHeight: "92vh",
          overflow: "hidden",
          display: "flex",
          flexDirection: "column",
          boxShadow: "0 20px 60px rgba(0,0,0,0.25)",
        }}
      >
        <div
          style={{
            padding: "1.1rem 1.3rem",
            borderBottom: "1px solid #e4dccc",
            display: "flex",
            alignItems: "center",
            justifyContent: "space-between",
            background: "#f8f3e8",
          }}
        >
          <div style={{ display: "flex", alignItems: "center", gap: "0.6rem" }}>
            <Receipt size={20} style={{ color: "#3d4a2a" }} />
            <h2
              className="font-display"
              style={{ margin: 0, fontSize: "1.2rem", fontWeight: 600 }}
            >
              {readonly
                ? "Einkauf-Details"
                : isEdit
                ? "Einkauf bearbeiten"
                : "Einkauf erfassen"}
            </h2>
          </div>
          <button className="icon-btn" onClick={onCancel} aria-label="Schließen">
            <X size={18} />
          </button>
        </div>

        <div
          style={{
            overflowY: "auto",
            padding: "1.2rem 1.3rem",
            display: "grid",
            gap: "1rem",
          }}
        >
          {readonly && (
            <div
              style={{
                display: "flex",
                alignItems: "flex-start",
                gap: "0.5rem",
                padding: "0.7rem 0.85rem",
                background: "rgba(61, 74, 42, 0.07)",
                borderLeft: "3px solid #3d4a2a",
                borderRadius: "0 6px 6px 0",
                fontSize: "0.85rem",
                color: "#4a443b",
                lineHeight: 1.45,
              }}
            >
              <CheckCircle2 size={15} style={{ color: "#3d4a2a", marginTop: "1px", flexShrink: 0 }} />
              <span>
                Dieser Einkauf ist bereits <strong>abgerechnet</strong> und kann nicht
                mehr bearbeitet werden. Um Änderungen vorzunehmen, muss zuerst
                die Abrechnung im Bereich „Kasse" rückgängig gemacht werden.
              </span>
            </div>
          )}
          <fieldset
            disabled={readonly}
            style={{
              border: "none",
              padding: 0,
              margin: 0,
              minWidth: 0,
              display: "grid",
              gap: "1rem",
              opacity: readonly ? 0.95 : 1,
            }}
          >
          {/* Betrag */}
          <label style={{ display: "block" }}>
            <div
              style={{
                fontSize: "0.78rem",
                fontWeight: 600,
                color: "#4a443b",
                marginBottom: "0.3rem",
                textTransform: "uppercase",
                letterSpacing: "0.05em",
              }}
            >
              Betrag *
            </div>
            <div style={{ position: "relative" }}>
              <input
                className="input-base"
                type="text"
                inputMode="decimal"
                placeholder="0,00"
                value={amountStr}
                onChange={(e) => setAmountStr(e.target.value)}
                style={{
                  width: "100%",
                  padding: "0.65rem 2.5rem 0.65rem 0.8rem",
                  fontSize: "1.1rem",
                  fontVariantNumeric: "tabular-nums",
                  textAlign: "right",
                }}
                autoFocus
              />
              <span
                style={{
                  position: "absolute",
                  right: "0.8rem",
                  top: "50%",
                  transform: "translateY(-50%)",
                  color: "#8a8275",
                  fontWeight: 500,
                  pointerEvents: "none",
                }}
              >
                €
              </span>
            </div>
          </label>

          {/* Bezahler */}
          <label style={{ display: "block" }}>
            <div
              style={{
                fontSize: "0.78rem",
                fontWeight: 600,
                color: "#4a443b",
                marginBottom: "0.3rem",
                textTransform: "uppercase",
                letterSpacing: "0.05em",
              }}
            >
              Bezahlt von *
            </div>
            <select
              className="input-base"
              value={paidBy}
              onChange={(e) => setPaidBy(e.target.value)}
              style={{ width: "100%", padding: "0.6rem 0.7rem" }}
            >
              {members.map((m) => (
                <option key={m.userId} value={m.userId}>
                  {memberDisplayName(m)}
                  {m.userId === currentUserId ? " (du)" : ""}
                </option>
              ))}
            </select>
          </label>

          {/* Datum + Geschäft */}
          <div
            style={{
              display: "grid",
              gridTemplateColumns: "1fr 1fr",
              gap: "0.75rem",
            }}
          >
            <label>
              <div
                style={{
                  fontSize: "0.78rem",
                  fontWeight: 600,
                  color: "#4a443b",
                  marginBottom: "0.3rem",
                  textTransform: "uppercase",
                  letterSpacing: "0.05em",
                }}
              >
                Datum
              </div>
              <input
                className="input-base"
                type="date"
                value={purchasedAt}
                onChange={(e) => setPurchasedAt(e.target.value)}
                style={{ width: "100%", padding: "0.6rem 0.7rem" }}
              />
            </label>
            <label>
              <div
                style={{
                  fontSize: "0.78rem",
                  fontWeight: 600,
                  color: "#4a443b",
                  marginBottom: "0.3rem",
                  textTransform: "uppercase",
                  letterSpacing: "0.05em",
                }}
              >
                Geschäft
              </div>
              <input
                className="input-base"
                type="text"
                placeholder="z.B. Edeka"
                value={store}
                onChange={(e) => setStore(e.target.value)}
                style={{ width: "100%", padding: "0.6rem 0.7rem" }}
              />
            </label>
          </div>

          {/* Splitmodus */}
          <div>
            <div
              style={{
                fontSize: "0.78rem",
                fontWeight: 600,
                color: "#4a443b",
                marginBottom: "0.3rem",
                textTransform: "uppercase",
                letterSpacing: "0.05em",
              }}
            >
              Aufteilung
            </div>
            <div
              style={{
                display: "grid",
                gridTemplateColumns: "repeat(auto-fit, minmax(140px, 1fr))",
                gap: "0.4rem",
              }}
            >
              {[
                { id: "equal", label: "Gleich aufteilen", desc: "Pro Kopf" },
                { id: "shares", label: "Anteile", desc: "z.B. 2:1:1" },
                { id: "exact", label: "Exakte Beträge", desc: "in €" },
                { id: "payer_only", label: "Nur Bezahler", desc: "kein Split" },
              ].map((m) => (
                <button
                  key={m.id}
                  type="button"
                  onClick={() => setSplitMode(m.id)}
                  style={{
                    padding: "0.5rem 0.6rem",
                    border:
                      splitMode === m.id
                        ? "1.5px solid #3d4a2a"
                        : "1px solid #e4dccc",
                    background:
                      splitMode === m.id
                        ? "rgba(61, 74, 42, 0.08)"
                        : "transparent",
                    borderRadius: "8px",
                    cursor: "pointer",
                    textAlign: "left",
                    fontFamily: "inherit",
                  }}
                >
                  <div
                    style={{
                      fontSize: "0.85rem",
                      fontWeight: 600,
                      color: "#2a2620",
                    }}
                  >
                    {m.label}
                  </div>
                  <div
                    style={{
                      fontSize: "0.72rem",
                      color: "#8a8275",
                    }}
                  >
                    {m.desc}
                  </div>
                </button>
              ))}
            </div>
          </div>

          {/* Mitglieder-spezifische Felder */}
          {splitMode !== "payer_only" && members.length > 1 && (
            <div
              style={{
                background: "#f5efe4",
                border: "1px solid #e4dccc",
                borderRadius: "8px",
                padding: "0.7rem 0.85rem",
              }}
            >
              <div
                style={{
                  fontSize: "0.78rem",
                  fontWeight: 600,
                  color: "#4a443b",
                  marginBottom: "0.5rem",
                  textTransform: "uppercase",
                  letterSpacing: "0.05em",
                }}
              >
                {splitMode === "equal"
                  ? "Wer war dabei?"
                  : splitMode === "shares"
                  ? "Anteile pro Person"
                  : "Exakte Beträge pro Person"}
              </div>
              <div style={{ display: "grid", gap: "0.4rem" }}>
                {members.map((m) => {
                  const excluded = excludedMembers.has(m.userId);
                  return (
                    <div
                      key={m.userId}
                      style={{
                        display: "flex",
                        alignItems: "center",
                        gap: "0.5rem",
                        opacity: excluded ? 0.45 : 1,
                      }}
                    >
                      <label
                        style={{
                          display: "flex",
                          alignItems: "center",
                          gap: "0.4rem",
                          flex: 1,
                          cursor: "pointer",
                          fontSize: "0.9rem",
                        }}
                      >
                        <input
                          type="checkbox"
                          checked={!excluded}
                          onChange={() => toggleExcluded(m.userId)}
                        />
                        {memberDisplayName(m)}
                        {m.userId === currentUserId && (
                          <span style={{ color: "#8a8275" }}>(du)</span>
                        )}
                      </label>
                      {!excluded && splitMode === "shares" && (
                        <input
                          className="input-base"
                          type="text"
                          inputMode="decimal"
                          placeholder="1"
                          value={splitData[m.userId] ?? ""}
                          onChange={(e) =>
                            setSplitData((prev) => ({
                              ...prev,
                              [m.userId]: e.target.value,
                            }))
                          }
                          style={{
                            width: "5rem",
                            padding: "0.35rem 0.5rem",
                            textAlign: "right",
                          }}
                        />
                      )}
                      {!excluded && splitMode === "exact" && (
                        <div style={{ position: "relative" }}>
                          <input
                            className="input-base"
                            type="text"
                            inputMode="decimal"
                            placeholder="0,00"
                            value={splitData[m.userId] ?? ""}
                            onChange={(e) =>
                              setSplitData((prev) => ({
                                ...prev,
                                [m.userId]: e.target.value,
                              }))
                            }
                            style={{
                              width: "6rem",
                              padding: "0.35rem 1.4rem 0.35rem 0.5rem",
                              textAlign: "right",
                              fontVariantNumeric: "tabular-nums",
                            }}
                          />
                          <span
                            style={{
                              position: "absolute",
                              right: "0.4rem",
                              top: "50%",
                              transform: "translateY(-50%)",
                              color: "#8a8275",
                              fontSize: "0.8rem",
                              pointerEvents: "none",
                            }}
                          >
                            €
                          </span>
                        </div>
                      )}
                    </div>
                  );
                })}
              </div>
            </div>
          )}

          {/* Wochen-Verknüpfung */}
          {availableWeeks.length > 0 && (
            <div>
              <div
                style={{
                  fontSize: "0.78rem",
                  fontWeight: 600,
                  color: "#4a443b",
                  marginBottom: "0.3rem",
                  textTransform: "uppercase",
                  letterSpacing: "0.05em",
                }}
              >
                Wochen verknüpfen{" "}
                <span style={{ color: "#8a8275", fontWeight: 400, textTransform: "none" }}>
                  (optional)
                </span>
              </div>
              <div
                style={{
                  display: "flex",
                  gap: "0.3rem",
                  flexWrap: "wrap",
                }}
              >
                {availableWeeks.map((k) => {
                  const monday = new Date(k);
                  const active = weekStarts.has(k);
                  return (
                    <button
                      key={k}
                      type="button"
                      onClick={() => toggleWeek(k)}
                      style={{
                        padding: "0.35rem 0.6rem",
                        border: active
                          ? "1.5px solid #3d4a2a"
                          : "1px solid #e4dccc",
                        background: active
                          ? "rgba(61, 74, 42, 0.08)"
                          : "transparent",
                        borderRadius: "6px",
                        fontSize: "0.8rem",
                        cursor: "pointer",
                        fontFamily: "inherit",
                      }}
                    >
                      KW {getWeekNumber(monday)}
                    </button>
                  );
                })}
              </div>
            </div>
          )}

          {/* Quittungs-Foto */}
          <div>
            <div
              style={{
                fontSize: "0.78rem",
                fontWeight: 600,
                color: "#4a443b",
                marginBottom: "0.3rem",
                textTransform: "uppercase",
                letterSpacing: "0.05em",
              }}
            >
              Quittung{" "}
              <span style={{ color: "#8a8275", fontWeight: 400, textTransform: "none" }}>
                (optional)
              </span>
            </div>
            <input
              ref={fileInputRef}
              type="file"
              accept="image/*"
              capture="environment"
              onChange={handleFilePick}
              style={{ display: "none" }}
            />
            {pendingFile || existingReceiptPath ? (
              <div
                style={{
                  display: "flex",
                  alignItems: "center",
                  gap: "0.75rem",
                  padding: "0.6rem 0.75rem",
                  background: "#f5efe4",
                  border: "1px solid #e4dccc",
                  borderRadius: "8px",
                }}
              >
                <button
                  type="button"
                  onClick={() => setShowLightbox(true)}
                  title="Vergrößern"
                  style={{
                    width: "3rem",
                    height: "3rem",
                    border: "1.5px solid #e4dccc",
                    borderRadius: "6px",
                    overflow: "hidden",
                    background: "#fdfaf3",
                    cursor: "pointer",
                    padding: 0,
                    flexShrink: 0,
                    position: "relative",
                  }}
                >
                  {(localPreviewUrl || existingReceiptUrl) ? (
                    <img
                      src={localPreviewUrl || existingReceiptUrl}
                      alt="Quittung"
                      style={{
                        width: "100%",
                        height: "100%",
                        objectFit: "cover",
                      }}
                    />
                  ) : (
                    <div
                      style={{
                        width: "100%",
                        height: "100%",
                        display: "flex",
                        alignItems: "center",
                        justifyContent: "center",
                        color: "#bfb5a3",
                      }}
                    >
                      <Receipt size={18} />
                    </div>
                  )}
                </button>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div
                    style={{
                      fontSize: "0.85rem",
                      fontWeight: 500,
                      color: "#2a2620",
                    }}
                  >
                    {pendingFile
                      ? "Neue Quittung ausgewählt"
                      : "Quittung gespeichert"}
                  </div>
                  <div style={{ fontSize: "0.78rem", color: "#6b6358" }}>
                    {pendingFile
                      ? `${(pendingFile.size / 1024).toFixed(0)} KB · wird beim Speichern hochgeladen`
                      : "Klicke das Bild zum Vergrößern"}
                  </div>
                </div>
                <button
                  type="button"
                  className="btn-ghost"
                  onClick={() => fileInputRef.current?.click()}
                  style={{ padding: "0.35rem 0.6rem", fontSize: "0.82rem" }}
                  title="Ersetzen"
                >
                  <RefreshCw size={13} />
                </button>
                <button
                  type="button"
                  className="btn-ghost btn-danger"
                  onClick={removeReceipt}
                  style={{ padding: "0.35rem 0.6rem", fontSize: "0.82rem" }}
                  title="Entfernen"
                >
                  <X size={13} />
                </button>
              </div>
            ) : (
              <button
                type="button"
                onClick={() => fileInputRef.current?.click()}
                style={{
                  width: "100%",
                  padding: "0.7rem 0.9rem",
                  border: "1.5px dashed #c06a43",
                  background: "transparent",
                  color: "#c06a43",
                  borderRadius: "8px",
                  cursor: "pointer",
                  fontFamily: "inherit",
                  fontSize: "0.88rem",
                  fontWeight: 500,
                  display: "inline-flex",
                  alignItems: "center",
                  justifyContent: "center",
                  gap: "0.5rem",
                }}
              >
                <Camera size={16} /> Foto aufnehmen oder hochladen
              </button>
            )}
          </div>

          {/* Notiz */}
          <label>
            <div
              style={{
                fontSize: "0.78rem",
                fontWeight: 600,
                color: "#4a443b",
                marginBottom: "0.3rem",
                textTransform: "uppercase",
                letterSpacing: "0.05em",
              }}
            >
              Notiz{" "}
              <span style={{ color: "#8a8275", fontWeight: 400, textTransform: "none" }}>
                (optional)
              </span>
            </div>
            <input
              className="input-base"
              type="text"
              placeholder="z.B. Wocheneinkauf"
              value={note}
              onChange={(e) => setNote(e.target.value)}
              style={{ width: "100%", padding: "0.6rem 0.7rem" }}
            />
          </label>

          {error && (
            <div
              style={{
                background: "rgba(168, 66, 42, 0.1)",
                border: "1px solid rgba(168, 66, 42, 0.3)",
                color: "#a8422a",
                padding: "0.55rem 0.75rem",
                borderRadius: "6px",
                fontSize: "0.85rem",
                display: "flex",
                alignItems: "center",
                gap: "0.4rem",
              }}
            >
              <AlertCircle size={14} /> {error}
            </div>
          )}
          </fieldset>
        </div>

        <div
          style={{
            padding: "0.9rem 1.3rem",
            borderTop: "1px solid #e4dccc",
            display: "flex",
            justifyContent: "flex-end",
            gap: "0.5rem",
            background: "#f8f3e8",
          }}
        >
          {readonly ? (
            <button className="btn-primary" onClick={onCancel}>
              <X size={15} /> Schließen
            </button>
          ) : (
            <>
              <button className="btn-ghost" onClick={onCancel} disabled={saving}>
                Abbrechen
              </button>
              <button
                className="btn-primary"
                onClick={handleSave}
                disabled={saving || !isAmountValid}
              >
                <Check size={15} />{" "}
                {uploadingReceipt
                  ? "Lade Foto hoch…"
                  : saving
                  ? "Speichern…"
                  : "Speichern"}
              </button>
            </>
          )}
        </div>
      </div>

      {showLightbox && (existingReceiptPath || localPreviewUrl) && (
        <>
          {existingReceiptPath && !pendingFile ? (
            <ReceiptLightbox
              path={existingReceiptPath}
              onClose={() => setShowLightbox(false)}
            />
          ) : localPreviewUrl ? (
            <div
              onClick={() => setShowLightbox(false)}
              style={{
                position: "fixed",
                inset: 0,
                background: "rgba(0,0,0,0.85)",
                zIndex: 300,
                display: "flex",
                alignItems: "center",
                justifyContent: "center",
                padding: "1rem",
              }}
            >
              <button
                onClick={() => setShowLightbox(false)}
                aria-label="Schließen"
                style={{
                  position: "absolute",
                  top: "1rem",
                  right: "1rem",
                  background: "rgba(255,255,255,0.15)",
                  color: "#fff",
                  border: "none",
                  borderRadius: "50%",
                  width: "2.5rem",
                  height: "2.5rem",
                  cursor: "pointer",
                  display: "flex",
                  alignItems: "center",
                  justifyContent: "center",
                }}
              >
                <X size={20} />
              </button>
              <img
                src={localPreviewUrl}
                alt="Quittung"
                onClick={(e) => e.stopPropagation()}
                style={{
                  maxWidth: "95vw",
                  maxHeight: "90vh",
                  objectFit: "contain",
                  borderRadius: "8px",
                }}
              />
            </div>
          ) : null}
        </>
      )}
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Mount
// ────────────────────────────────────────────────────────────────────
const rootEl = document.getElementById("root");
if (rootEl) {
  createRoot(rootEl).render(<MealPlannerApp />);
}
