// Orchestrator — fetches from /api/*, holds state, renders views.

const { useState, useEffect, useCallback } = React;

function useLocalStorage(key, initial) {
  const [value, setValue] = useState(() => {
    try { const v = localStorage.getItem(key); return v == null ? initial : JSON.parse(v); }
    catch { return initial; }
  });
  useEffect(() => {
    try { localStorage.setItem(key, JSON.stringify(value)); } catch {}
  }, [key, value]);
  return [value, setValue];
}

function Toast({ message, onHide }) {
  useEffect(() => {
    if (!message) return;
    const t = setTimeout(onHide, 2800);
    return () => clearTimeout(t);
  }, [message, onHide]);
  if (!message) return null;
  return <div className="toast-bar">{message}</div>;
}

function App() {
  if (window.location.pathname === '/sign-in') return <SignInPage />;
  if (window.location.pathname === '/sign-up') return <SignUpPage />;

  const { isLoaded, isSignedIn, getAuthHeaders, openSignIn, openSignUp } = useAuthState();

  // Redirect expired/unauthenticated sessions away from the app to the marketing home.
  // isLoaded guard prevents a redirect flash during Clerk's async init (~200ms).
  useEffect(() => {
    if (isLoaded && !isSignedIn && window.location.pathname === '/app') {
      window.location.replace('/');
    }
  }, [isLoaded, isSignedIn]);

  const [recipes, setRecipes] = useState([]);
  const [loaded, setLoaded] = useState(false);
  // undefined = not yet loaded, null = signed-in but no family, object = has family
  const [familyData, setFamilyData] = useState(undefined);
  // Check if we landed on /invite/:token
  const _inviteToken = (() => {
    const m = window.location.pathname.match(/^\/invite\/([a-f0-9]{64})$/);
    return m ? m[1] : null;
  })();
  const [view, setView] = useState(_inviteToken ? { screen: 'invite', token: _inviteToken } : { screen: 'home' });
  const [obName, setObName] = useState('');
  const [obBusy, setObBusy] = useState(false);
  const [obErr,  setObErr]  = useState('');
  // 'waitlist' | 'open' — from /api/config. Defaults to waitlist (fail closed)
  // until loaded, so a stranger never sees the self-serve onboarding by accident.
  const [launchMode, setLaunchMode] = useState('waitlist');
  useEffect(() => {
    fetch('/api/config').then(r => r.json())
      .then(c => setLaunchMode(c.launchMode === 'open' ? 'open' : 'waitlist'))
      .catch(() => {});
  }, []);
  const [filters, setFilters] = useState({ search: '', meal: '', meat: '', addedBy: '', author: '', favOnly: false, quickEasy: false });
  const [shopping, setShopping] = useState([]);
  const [showShopping, setShowShopping] = useState(false);
  const [peekId, setPeekId] = useState(null);
  const [formState, setFormState] = useState(null); // null | { recipe?: {...} }
  const [importOpen, setImportOpen] = useState(false);
  const [scanOpen, setScanOpen] = useState(false);
  const [scanImportData, setScanImportData] = useState(null);
  const [toastMsg, setToastMsg] = useState('');

  const [familyName, setFamilyName]       = useLocalStorage('cb.familyName', 'The Family Cookbook');
  const [familyTagline, setFamilyTagline] = useLocalStorage('cb.familyTagline', 'Treasured recipes, passed down with love.');
  // Cover photo lives in the DB (families.cover_image) and is served from R2.
  const familyPhoto = familyData?.family?.coverImage || '';
  const [cardStyle] = useLocalStorage('cb.cardStyle', 'editorial');

  const toast = useCallback((msg) => setToastMsg(msg), []);

  function requireSignIn(msg = 'Sign in to add or edit recipes') {
    if (!isSignedIn) {
      toast(msg);
      openSignIn();
      return false;
    }
    return true;
  }

  // ── Data ─────────────────────────────────────────
  useEffect(() => {
    (async () => {
      try {
        const sRes = await fetch('/api/shopping-list');
        const sList = await sRes.json();
        setShopping(Array.isArray(sList) ? sList : []);
      } catch {}
      finally { setLoaded(true); }
    })();
  }, []);

  // Fetch family + recipes and return the raw data (no state side-effects).
  async function fetchFamilyAndRecipes() {
    const authHdrs = await getAuthHeaders();
    const [famRes, recRes] = await Promise.all([
      fetch('/api/family', { headers: authHdrs }),
      fetch('/api/recipes', { headers: authHdrs }),
    ]);
    const famJson = await famRes.json();
    const recList = await recRes.json();
    return {
      familyData: famJson.family ? famJson : null,
      recipes:    Array.isArray(recList) ? recList : [],
    };
  }

  async function reloadFamilyAndRecipes() {
    try {
      const data = await fetchFamilyAndRecipes();
      setFamilyData(data.familyData);
      setRecipes(data.recipes);
    } catch {
      setFamilyData(null);
    }
  }

  useEffect(() => {
    if (!isSignedIn) { setFamilyData(null); setRecipes([]); return; }
    setFamilyData(undefined); // loading — prevents "set up your family" flash during fetch
    reloadFamilyAndRecipes();
  }, [isSignedIn]);

  async function updateRecipe(full) {
    const authHdrs = await getAuthHeaders();
    const res = await fetch(`/api/recipes/${full.id}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json', ...authHdrs },
      body: JSON.stringify(full),
    });
    if (!res.ok) { toast('Save failed'); return null; }
    const updated = await res.json();
    setRecipes(rs => rs.map(r => r.id === updated.id ? updated : r));
    return updated;
  }

  async function createRecipe(payload) {
    const authHdrs = await getAuthHeaders();
    const res = await fetch('/api/recipes', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', ...authHdrs },
      body: JSON.stringify(payload),
    });
    if (!res.ok) { toast('Save failed'); return null; }
    const created = await res.json();
    setRecipes(rs => [...rs, created]);
    return created;
  }

  async function addRating(recipeId, { name, stars, note }) {
    if (!requireSignIn()) return;
    const base = recipes.find(x => x.id === recipeId);
    if (!base) return;
    const newRating = {
      id: Date.now() + Math.floor(Math.random() * 100000),
      name: name || 'Anonymous',
      stars,
      note: note || '',
      date: new Date().toISOString(),
    };
    const payload = { ...base, ratings: [...(base.ratings || []), newRating] };
    const res = await fetch(`/api/recipes/${recipeId}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    });
    if (!res.ok) { toast('Failed to save rating'); return; }
    const updated = await res.json();
    setRecipes(rs => rs.map(r => r.id === updated.id ? updated : r));
    toast('Rating added');
  }

  async function setVerdict(recipeId, payload) {
    const authHdrs = await getAuthHeaders();
    const res = await fetch(`/api/recipes/${recipeId}/verdict`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json', ...authHdrs },
      body: JSON.stringify(payload),
    });
    const data = await res.json();
    if (!res.ok) { toast(data.error || 'Could not save your vote'); return; }
    setRecipes(rs => rs.map(r => r.id === recipeId ? { ...r, verdicts: data.verdicts } : r));
  }

  async function removeVerdict(recipeId) {
    const authHdrs = await getAuthHeaders();
    const res = await fetch(`/api/recipes/${recipeId}/verdict`, { method: 'DELETE', headers: authHdrs });
    const data = await res.json();
    if (!res.ok) { toast(data.error || 'Could not remove your vote'); return; }
    setRecipes(rs => rs.map(r => r.id === recipeId ? { ...r, verdicts: data.verdicts } : r));
  }

  async function deleteRating(recipeId, ratingId) {
    if (!requireSignIn()) return;
    const base = recipes.find(x => x.id === recipeId);
    if (!base) return;
    const payload = { ...base, ratings: (base.ratings || []).filter(rt => rt.id !== ratingId) };
    const res = await fetch(`/api/recipes/${recipeId}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    });
    if (!res.ok) { toast('Failed to delete'); return; }
    const updated = await res.json();
    setRecipes(rs => rs.map(r => r.id === updated.id ? updated : r));
    toast('Rating deleted');
  }

  async function addComment(recipeId, { author, text }) {
    if (!requireSignIn()) return;
    try {
      const authHdrs = await getAuthHeaders();
      const res = await fetch(`/api/recipes/${recipeId}/comments`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', ...authHdrs },
        body: JSON.stringify({ author, text }),
      });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      const comment = await res.json();
      setRecipes(rs => rs.map(r => r.id === recipeId
        ? { ...r, comments: [...(r.comments || []), comment] }
        : r));
      toast('Note added');
    } catch {
      toast('Failed to add note');
    }
  }

  async function deleteComment(recipeId, commentId) {
    if (!requireSignIn()) return;
    try {
      const authHdrs = await getAuthHeaders();
      const res = await fetch(`/api/recipes/${recipeId}/comments/${commentId}`, {
        method: 'DELETE',
        headers: { ...authHdrs },
      });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      setRecipes(rs => rs.map(r => r.id === recipeId
        ? { ...r, comments: (r.comments || []).filter(c => c.id !== commentId) }
        : r));
      toast('Note deleted');
    } catch {
      toast('Failed to delete note');
    }
  }

  async function handleFormSave(payload) {
    if (!requireSignIn()) return;
    if (payload.id) {
      const updated = await updateRecipe(payload);
      if (updated) { setFormState(null); toast('Recipe updated'); }
    } else {
      const created = await createRecipe(payload);
      if (created) {
        setFormState(null);
        setView({ screen: 'recipe', id: created.id });
        window.scrollTo({ top: 0 });
        toast('Recipe added');
      }
    }
  }

  async function enhancePhoto(recipeId, newImageUrl) {
    const base = recipes.find(x => x.id === recipeId);
    if (!base) return;
    const updated = await updateRecipe({ ...base, image: newImageUrl });
    if (updated) toast('Photo updated ✓');
  }

  async function deleteRecipe(id) {
    if (!requireSignIn()) return;
    if (!confirm('Delete this recipe? This cannot be undone.')) return;
    const authHdrs = await getAuthHeaders();
    const res = await fetch(`/api/recipes/${id}`, { method: 'DELETE', headers: { ...authHdrs } });
    if (!res.ok) { toast('Delete failed'); return; }
    setRecipes(rs => rs.filter(r => r.id !== id));
    setView({ screen: 'home' });
    toast('Recipe deleted');
  }

  // ── Family ───────────────────────────────────────
  async function uploadFamilyPhoto(file) {
    const authHdrs = await getAuthHeaders();
    const form = new FormData();
    form.append('image', file);
    const res = await fetch('/api/family/cover-photo', { method: 'POST', headers: authHdrs, body: form });
    const data = await res.json();
    if (!res.ok) throw new Error(data.error || 'Upload failed');
    setFamilyData(prev => prev ? { ...prev, family: { ...prev.family, coverImage: data.url } } : prev);
  }

  async function createFamily(name) {
    const authHdrs = await getAuthHeaders();
    const res = await fetch('/api/family/create', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', ...authHdrs },
      body: JSON.stringify({ name }),
    });
    const data = await res.json();
    if (!res.ok) throw new Error(data.error || 'Could not create family');
    // Reload family + recipes
    const [famRes, recRes] = await Promise.all([
      fetch('/api/family', { headers: authHdrs }),
      fetch('/api/recipes', { headers: authHdrs }),
    ]);
    const famJson = await famRes.json();
    const recList = await recRes.json();
    setFamilyData(famJson.family ? famJson : null);
    setRecipes(Array.isArray(recList) ? recList : []);
    toast('Family created!');
  }

  async function renameFamily(newName) {
    const authHdrs = await getAuthHeaders();
    const res = await fetch('/api/family/name', {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json', ...authHdrs },
      body: JSON.stringify({ name: newName }),
    });
    if (!res.ok) { toast('Could not rename family'); return; }
    setFamilyData(fd => fd ? { ...fd, family: { ...fd.family, name: newName } } : fd);
    toast('Family renamed');
  }

  async function removeMember(userId) {
    const authHdrs = await getAuthHeaders();
    const res = await fetch(`/api/family/members/${userId}`, {
      method: 'DELETE',
      headers: authHdrs,
    });
    if (!res.ok) { toast('Could not remove member'); return; }
    setFamilyData(fd => fd ? { ...fd, members: fd.members.filter(m => m.userId !== userId) } : fd);
    toast('Member removed');
  }

  const myUserId = familyData?.members?.find(m => m.id === familyData.currentMemberId)?.userId || null;

  async function toggleHeart(recipe) {
    const mine = (recipe.hearts || []).includes(myUserId);
    const authHdrs = await getAuthHeaders();
    const res = await fetch(`/api/recipes/${recipe.id}/heart`, {
      method: mine ? 'DELETE' : 'PUT',
      headers: authHdrs,
    });
    const data = await res.json();
    if (!res.ok) { toast(data.error || 'Could not save'); return; }
    setRecipes(rs => rs.map(r => r.id === recipe.id ? { ...r, hearts: data.hearts } : r));
  }

  function toggleFav(recipe) {
    if (!requireSignIn()) return;
    updateRecipe({ ...recipe, favorite: !recipe.favorite });
  }

  // ── Shopping list ────────────────────────────────
  async function saveShopping(items) {
    setShopping(items);
    try {
      await fetch('/api/shopping-list', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ items }),
      });
    } catch { toast('Could not save list'); }
  }

  function addToShopping(recipe, factor = 1) {
    const existing = new Set(
      shopping.filter(s => s.fromRecipeId === recipe.id)
              .map(s => `${(s.amt || '').toLowerCase()}||${(s.name || '').toLowerCase()}`)
    );
    const added = [];
    for (const i of recipe.ingredients || []) {
      const amt = factor === 1 ? (i.amt || '') : scaleAmtStr(i.amt || '', factor);
      const name = i.name || '';
      if (!name.trim()) continue;
      const key = `${amt.toLowerCase()}||${name.toLowerCase()}`;
      if (existing.has(key)) continue;
      existing.add(key);
      added.push({
        id: Date.now() + Math.floor(Math.random() * 100000),
        amt, name,
        checked: false,
        fromRecipeId: recipe.id,
        addedAt: new Date().toISOString(),
      });
    }
    if (!added.length) { toast('Already in your list'); setShowShopping(true); return; }
    saveShopping([...shopping, ...added]);
    setShowShopping(true);
    toast(`Added ${added.length} item${added.length === 1 ? '' : 's'}`);
  }

  // Bulk add (used by the planner's "add week to list"): one state write, one toast.
  function addRecipesToShopping(recipeList) {
    const existing = new Set(
      shopping.map(x => `${x.fromRecipeId}||${(x.amt || '').toLowerCase()}||${(x.name || '').toLowerCase()}`)
    );
    const added = [];
    for (const recipe of recipeList) {
      for (const i of recipe.ingredients || []) {
        const name = (i.name || '').trim();
        if (!name) continue;
        const key = `${recipe.id}||${(i.amt || '').toLowerCase()}||${name.toLowerCase()}`;
        if (existing.has(key)) continue;
        existing.add(key);
        added.push({
          id: Date.now() + Math.floor(Math.random() * 1000000) + added.length,
          amt: i.amt || '', name,
          checked: false,
          fromRecipeId: recipe.id,
          addedAt: new Date().toISOString(),
        });
      }
    }
    if (!added.length) { toast('Everything is already on your list'); setShowShopping(true); return; }
    saveShopping([...shopping, ...added]);
    setShowShopping(true);
    toast(`Added ${added.length} ingredient${added.length === 1 ? '' : 's'} from ${recipeList.length} recipe${recipeList.length === 1 ? '' : 's'}`);
  }

  function toggleShopping(id) {
    const next = shopping.map(s => s.id === id ? { ...s, checked: !s.checked } : s);
    saveShopping(next);
  }
  function clearShoppingChecked() { saveShopping(shopping.filter(s => !s.checked)); }
  function clearShoppingAll() {
    if (!shopping.length) return;
    if (confirm('Clear the entire shopping list?')) saveShopping([]);
  }

  // ── Nav helpers ──────────────────────────────────
  const activeRecipe = (view.screen === 'recipe' || view.screen === 'cook')
    ? recipes.find(r => r.id === view.id)
    : null;

  // ── Render ───────────────────────────────────────
  if (!loaded) return (
    <div className="boot-screen">
      <div className="bs-script">{familyName || 'The Family Cookbook'}</div>
      <div className="bs-sub">the family collection</div>
    </div>
  );

  return (
    <>
      <Header
        onHome={() => setView({ screen: 'home' })}
        onSearch={(s) => setFilters(f => ({ ...f, search: s }))}
        searchValue={filters.search}
        onAdd={() => { if (requireSignIn()) setFormState({ recipe: null }); }}
        onImport={() => { if (requireSignIn('Sign in to import recipes')) setImportOpen(true); }}
        onScan={() => { if (requireSignIn('Sign in to scan recipes')) setScanOpen(true); }}
        onShopping={() => setShowShopping(true)}
        shoppingCount={shopping.filter(s => !s.checked).length}
        onRoulette={() => setView({ screen: 'roulette' })}
        onPlanner={() => { if (requireSignIn('Sign in to use the meal planner')) setView({ screen: 'planner' }); }}
        onFamily={() => { if (requireSignIn('Sign in to manage your family')) setView({ screen: 'family' }); }}
        isSignedIn={isSignedIn}
        currentView={view.screen}
        familyName={familyName}
      />

      {view.screen === 'family' && (
        <FamilySettingsView
          familyData={familyData}
          onBack={() => setView({ screen: 'home' })}
          onCreateFamily={createFamily}
          onRenameFamily={renameFamily}
          onRemoveMember={removeMember}
          getAuthHeaders={getAuthHeaders}
          onAvatarSaved={(avatar) => setFamilyData(fd => {
            if (!fd) return fd;
            return { ...fd, members: fd.members.map(m => m.id === fd.currentMemberId ? { ...m, avatar } : m) };
          })}
        />
      )}

      {view.screen === 'invite' && (
        <InviteAcceptPage
          token={view.token}
          isSignedIn={isSignedIn}
          familyData={familyData}
          getAuthHeaders={getAuthHeaders}
          openSignIn={openSignIn}
          openSignUp={openSignUp}
          onAccepted={async () => {
            window.history.replaceState(null, '', '/app');
            try {
              const data = await fetchFamilyAndRecipes();
              // All three setters in one synchronous batch — home view
              // renders once with family + recipes already populated.
              setFamilyData(data.familyData);
              setRecipes(data.recipes);
            } catch {}
            setView({ screen: 'home' });
          }}
        />
      )}

      {/* Signed in, no family, doors not open: waitlist instead of onboarding.
          The server also refuses POST /api/family/create in this mode. */}
      {view.screen === 'home' && isSignedIn && familyData === null && launchMode !== 'open' && (
        <div className="onboard">
          <WaitlistForm source="in-app" compact />
        </div>
      )}

      {view.screen === 'home' && isSignedIn && familyData === null && launchMode === 'open' && (
        <div className="onboard">
          <p className="ob-eyebrow">welcome to the kitchen</p>
          <h2>Name your cookbook.</h2>
          <p className="ob-sub">
            This becomes the cover of your family's keepsake — every recipe, note
            and scribble lives under it. You can change it anytime.
          </p>
          <input
            className="ob-input"
            value={obName}
            onChange={e => setObName(e.target.value)}
            placeholder="The Moretti Family Cookbook"
            maxLength={60}
            autoFocus
          />
          {obErr && <p className="ob-err">{obErr}</p>}
          <button className="primary-btn big" disabled={obBusy} onClick={async () => {
            const name = obName.trim() || 'Our Family Cookbook';
            setObBusy(true); setObErr('');
            try {
              await createFamily(name);
              setFamilyName(name);
            } catch (e) {
              setObErr(e.message);
            }
            setObBusy(false);
          }}>
            {obBusy ? 'Setting the table…' : 'Start volume one'}
          </button>
          <p className="ob-skip">blank is fine too — we'll call it "Our Family Cookbook"</p>
        </div>
      )}

      {view.screen === 'home' && !(isSignedIn && familyData === null) && (
        <HomeView
          recipes={recipes}
          onOpen={(id) => { setView({ screen: 'recipe', id }); window.scrollTo({top:0}); }}
          filters={filters}
          setFilters={setFilters}
          cardStyle={cardStyle}
          familyPhoto={familyPhoto}
          setFamilyPhoto={uploadFamilyPhoto}
          familyName={familyName}
          setFamilyName={setFamilyName}
          familyTagline={familyTagline}
          onRoulette={() => setView({ screen: 'roulette' })}
          onPlanner={() => { if (requireSignIn('Sign in to use the meal planner')) setView({ screen: 'planner' }); }}
          onToggleFav={(r) => { if (requireSignIn()) toggleHeart(r); }}
          myUserId={myUserId}
          members={familyData?.members || []}
        />
      )}

      {view.screen === 'planner' && (
        <PlannerView
          recipes={recipes}
          getAuthHeaders={getAuthHeaders}
          onBack={() => setView({ screen: 'home' })}
          onAddWeekToList={addRecipesToShopping}
        />
      )}

      {view.screen === 'roulette' && (
        <RouletteView
          recipes={recipes}
          filters={filters}
          onOpenRecipe={(id) => { setView({ screen: 'recipe', id }); window.scrollTo({ top: 0 }); }}
          onBack={() => setView({ screen: 'home' })}
          onClearFilters={() => setFilters({ search: '', meal: '', meat: '', addedBy: '', author: '', favOnly: false, quickEasy: false })}
          onAddRecipe={() => { setView({ screen: 'home' }); setFormState({ recipe: null }); }}
        />
      )}

      {view.screen === 'recipe' && activeRecipe && (
        <RecipeView
          recipe={activeRecipe}
          allRecipes={recipes}
          onBack={() => { setView({ screen: 'home' }); window.scrollTo({top:0}); }}
          onToggleFav={() => { if (requireSignIn()) toggleHeart(activeRecipe); }}
          myUserId={myUserId}
          onAddToShopping={(r, factor) => addToShopping(r, factor)}
          onCook={() => setView({ screen: 'cook', id: activeRecipe.id })}
          onEdit={() => setFormState({ recipe: activeRecipe })}
          onDelete={() => deleteRecipe(activeRecipe.id)}
          onOpenReference={(id) => setPeekId(id)}
          onAddComment={(payload) => addComment(activeRecipe.id, payload)}
          onDeleteComment={(cid) => deleteComment(activeRecipe.id, cid)}
          onSetVerdict={(payload) => setVerdict(activeRecipe.id, payload)}
          onRemoveVerdict={() => removeVerdict(activeRecipe.id)}
          currentUserId={familyData?.members?.find(m => m.id === familyData.currentMemberId)?.userId || null}
          members={familyData?.members || []}
          onEnhancePhoto={(id, url) => enhancePhoto(id, url)}
        />
      )}

      {view.screen === 'cook' && activeRecipe && (
        <CookMode
          recipe={activeRecipe}
          onClose={() => setView({ screen: 'recipe', id: activeRecipe.id })}
        />
      )}

      {view.screen !== 'cook' && (
        <footer className="app-footer">
          <p className="f-script">"Every recipe in here is someone's memory."</p>
          <p className="f-small">{familyName || 'The Family Cookbook'} · a private family keepsake · no ads, ever</p>
        </footer>
      )}

      {showShopping && (
        <>
          <div className="shop-backdrop" onClick={() => setShowShopping(false)} />
          <ShoppingPanel
            items={shopping}
            onClose={() => setShowShopping(false)}
            onToggle={toggleShopping}
            onClearChecked={clearShoppingChecked}
            onClearAll={clearShoppingAll}
          />
        </>
      )}

      {formState && (
        <RecipeForm
          recipe={formState.recipe}
          onSave={handleFormSave}
          onCancel={() => setFormState(null)}
        />
      )}

      {importOpen && (
        <ImportUrlModal
          onClose={() => setImportOpen(false)}
          onImported={(data) => {
            setImportOpen(false);
            setFormState({ recipe: data });
          }}
        />
      )}

      {scanOpen && (
        <ScanModal
          onClose={() => setScanOpen(false)}
          onImported={(data) => {
            setScanOpen(false);
            // Clear any scan image — cookbook pages are not dish photos
            setScanImportData({ ...data, image: null });
          }}
        />
      )}

      {scanImportData && (
        <DishPhotoModal
          onDone={(imageUrl) => {
            const recipe = { ...scanImportData, image: imageUrl || null };
            setScanImportData(null);
            setFormState({ recipe });
          }}
        />
      )}

      {peekId && (() => {
        const r = recipes.find(x => x.id === peekId);
        if (!r) return null;
        return (
          <>
            <div className="peek-backdrop" onClick={() => setPeekId(null)} />
            <RecipePeek
              recipe={r}
              allRecipes={recipes}
              onClose={() => setPeekId(null)}
              onOpenRef={(id) => setPeekId(id)}
            />
          </>
        );
      })()}

      <Toast message={toastMsg} onHide={() => setToastMsg('')} />
    </>
  );
}

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