/* NutriDMS, App shell */
const { useState: aUseState, useEffect: aUseEffect, useCallback: aUseCallback } = React;

/* In-app wrapper for the standalone Add Ingredient page so Media Contributors
   keep the platform sidebar / topbar while filling the form. */
function AddIngredientPage() {
  return <CreationGate kind="ingredient"><IframeWithLoraaFade src="screens/create-ingredient.html?v=20260828-public-catalog-media-1" title="Add Ingredient" /></CreationGate>;
}

function LoraaCommandCenterRoute() {
  const { role, setPage } = useApp();
  const user = window.__nutridmsAuthenticatedUser || currentUser(role);
  return (
    <React.Fragment>
      <Dashboard />
      {typeof LoraaAsk === "function" && (
        <LoraaAsk role={role} user={user} open={true} onClose={() => setPage("dashboard")} />
      )}
    </React.Fragment>
  );
}

/* Gate: new recipes/ingredients cannot be created until the organization's
   reference-ID format has been configured (Settings → Reference IDs). */
function CreationGate({ kind, children }) {
  const { role } = useApp();
  const [referenceState, setReferenceState] = aUseState(() => window.__nutridmsReferenceIdState || {
    status: "loading", organizationId: "", config: null, error: ""
  });
  aUseEffect(() => {
    const onReferenceState = (event) => {
      if (event && event.detail) setReferenceState(event.detail);
    };
    window.addEventListener("nutridms-refid-state", onReferenceState);
    return () => window.removeEventListener("nutridms-refid-state", onReferenceState);
  }, []);
  const retry = () => {
    setReferenceState((current) => ({ ...current, status: "loading", error: "" }));
    if (window.NutriReferenceIds && typeof window.NutriReferenceIds.refresh === "function") {
      window.NutriReferenceIds.refresh().catch(() => {});
    }
  };
  const workspaceName = (window.NutriData && window.NutriData.orgName && window.NutriData.orgName()) || "this workspace";
  if (referenceState.status === "configured") return children;
  const canConfigure = role === "admin" || role === "super-admin";
  const setPage = window.__setPage || (() => {});

  if (referenceState.status === "loading") {
    return (
      <div className="page" style={{ display: "grid", placeItems: "center", minHeight: "calc(100vh - 160px)" }}>
        <div className="card" style={{ maxWidth: 520, textAlign: "center", padding: "40px 36px" }}>
          <div className="stat-icon" style={{ width: 64, height: 64, borderRadius: 16, margin: "0 auto 16px", display: "grid", placeItems: "center", background: "var(--green-50)", color: "var(--green-700)" }}>
            <Icon name="loader-circle" size={30} stroke={2} />
          </div>
          <h2 style={{ fontFamily: "var(--serif)", fontSize: 26, margin: "0 0 8px" }}>Checking reference IDs</h2>
          <p className="muted" style={{ fontSize: 14, lineHeight: 1.6, margin: 0 }}>
            Verifying the reference-ID format for {workspaceName} before opening the {kind === "ingredient" ? "ingredient" : "recipe"} editor.
          </p>
        </div>
      </div>
    );
  }

  if (referenceState.status === "error") {
    return (
      <div className="page" style={{ display: "grid", placeItems: "center", minHeight: "calc(100vh - 160px)" }}>
        <div className="card" style={{ maxWidth: 520, textAlign: "center", padding: "40px 36px" }}>
          <div className="stat-icon" style={{ width: 64, height: 64, borderRadius: 16, margin: "0 auto 16px", display: "grid", placeItems: "center", background: "var(--danger-50)", color: "var(--danger-600)" }}>
            <Icon name="wifi-off" size={30} stroke={2} />
          </div>
          <h2 style={{ fontFamily: "var(--serif)", fontSize: 26, margin: "0 0 8px" }}>Reference IDs could not be verified</h2>
          <p className="muted" style={{ fontSize: 14, lineHeight: 1.6, margin: "0 0 22px" }}>
            We could not verify the reference-ID settings for {workspaceName}. Please retry before creating content.
          </p>
          <button className="btn primary" onClick={retry}><Icon name="refresh-cw" size={15} /> Retry</button>
        </div>
      </div>
    );
  }

  return (
    <div className="page" style={{ display: "grid", placeItems: "center", minHeight: "calc(100vh - 160px)" }}>
      <div className="card" style={{ maxWidth: 520, textAlign: "center", padding: "40px 36px" }}>
        <div className="stat-icon" style={{ width: 64, height: 64, borderRadius: 16, margin: "0 auto 16px", display: "grid", placeItems: "center", background: "var(--warning-50)", color: "var(--warning-600)" }}>
          <Icon name="hash" size={30} stroke={2} />
        </div>
        <h2 style={{ fontFamily: "var(--serif)", fontSize: 26, margin: "0 0 8px" }}>Set up reference IDs first</h2>
        <p className="muted" style={{ fontSize: 14, lineHeight: 1.6, margin: "0 0 22px" }}>
          Every {kind === "ingredient" ? "ingredient" : "recipe"} needs a unique reference ID for auditability.
          {workspaceName}'s ID format must be configured before any new {kind === "ingredient" ? "ingredients" : "recipes"} can be created.
        </p>
        {canConfigure
          ? <button className="btn primary" onClick={() => { window.__settingsTab = "refids"; setPage("settings"); }}><Icon name="settings" size={15} /> Configure reference IDs</button>
          : <div className="refid-note" style={{ textAlign: "left", justifyContent: "flex-start" }}><Icon name="lock" size={15} stroke={2.2} /><span>Ask an Admin to configure the reference ID format in <strong>Settings → Reference IDs</strong>.</span></div>}
      </div>
    </div>);
}

/* Builder iframe wrapper that dims the whole app shell when the embedded Loraa
   popup opens (the iframe posts nutridms-loraa; clicking the dim closes it). */
function IframeWithLoraaFade({ src, title }) {
  const [loraaOpen, setLoraaOpen] = aUseState(false);
  aUseEffect(() => {
    const onMsg = (e) => { if (e.data && e.data.type === "nutridms-loraa") setLoraaOpen(!!e.data.open); };
    window.addEventListener("message", onMsg);
    return () => window.removeEventListener("message", onMsg);
  }, []);
  const closeLoraa = () => { const f = document.querySelector(`iframe[title="${title}"]`); try { f && f.contentWindow.postMessage({ type: "nutridms-loraa-close" }, "*"); } catch (e) {} };
  return (
    <div style={{ margin: "-32px -32px -32px", height: "calc(100vh - 64px)", display: "flex", flexDirection: "column", position: "relative", zIndex: loraaOpen ? 160 : "auto" }}>
      {loraaOpen && <div onMouseDown={closeLoraa} style={{ position: "fixed", inset: 0, background: "rgba(12,18,14,.5)", zIndex: 150, animation: "fade .16s ease" }} />}
      <iframe src={src} title={title} style={{ flex: 1, border: 0, width: "100%", height: "100%", background: "#FBFCF9", position: "relative", zIndex: loraaOpen ? 151 : "auto" }} />
    </div>);
}

/* Ingredients Library, mirrors Recipe Library UI */
const INGREDIENT_ITEMS = [
{ id: "ing_171477", name: "Chicken Breast, Skinless", canonical: "Gallus gallus domesticus", category: "Protein", status: "approved", reviewer: { name: "Eve Nakamura", initials: "EN", role: "Dietitian" }, contributor: { name: "Sarah Chen", initials: "SC" }, comments: 3, updated: "2026-05-15", priority: "medium", nutr: { calories: 165, p: 31, c: 0, f: 3.6 } },
{ id: "ing_169704", name: "Quinoa, cooked", canonical: "Chenopodium quinoa", category: "Grain", status: "approved", reviewer: { name: "Aisha Bello", initials: "AB", role: "Manager" }, contributor: { name: "Sarah Chen", initials: "SC" }, comments: 1, updated: "2026-05-08", priority: "low", nutr: { calories: 120, p: 4.4, c: 21.3, f: 1.9 } },
{ id: "ing_173441", name: "Avocado, raw", canonical: "Persea americana", category: "Fruit", status: "compliance-review", reviewer: { name: "Dana Liu", initials: "DL", role: "Compliance" }, contributor: { name: "Sarah Chen", initials: "SC" }, comments: 2, updated: "2026-05-12", priority: "high", nutr: { calories: 160, p: 2, c: 8.5, f: 14.7 } },
{ id: "ing_175167", name: "Olive Oil, extra virgin", canonical: "Olea europaea", category: "Oil & Fat", status: "approved", reviewer: { name: "Dana Liu", initials: "DL", role: "Compliance" }, contributor: { name: "Sarah Chen", initials: "SC" }, comments: 0, updated: "2026-05-27", priority: "low", nutr: { calories: 884, p: 0, c: 0, f: 100 } },
{ id: "ing_171287", name: "Spinach, raw", canonical: "Spinacia oleracea", category: "Vegetable", status: "changes-requested", reviewer: { name: "Eve Nakamura", initials: "EN", role: "Dietitian" }, contributor: { name: "Sarah Chen", initials: "SC" }, comments: 5, updated: "2026-05-26", priority: "high", nutr: { calories: 23, p: 2.9, c: 3.6, f: 0.4 } },
{ id: "ing_168474", name: "Greek Yogurt, plain", canonical: "Lactobacillus cultures", category: "Dairy", status: "approved", reviewer: { name: "Dana Liu", initials: "DL", role: "Compliance" }, contributor: { name: "Sarah Chen", initials: "SC" }, comments: 1, updated: "2026-05-25", priority: "medium", nutr: { calories: 97, p: 9, c: 3.6, f: 5 } },
{ id: "ing_175140", name: "Almonds, raw", canonical: "Prunus dulcis", category: "Nut", status: "pending-review", reviewer: null, contributor: { name: "Sarah Chen", initials: "SC" }, comments: 0, updated: "2026-05-23", priority: "medium", nutr: { calories: 579, p: 21.2, c: 21.6, f: 49.9 } },
{ id: "ing_168409", name: "Black Beans, cooked", canonical: "Phaseolus vulgaris", category: "Legume", status: "draft", reviewer: null, contributor: { name: "Sarah Chen", initials: "SC" }, comments: 0, updated: "2026-05-22", priority: "low", nutr: { calories: 132, p: 8.9, c: 23.7, f: 0.5 } },
{ id: "ing_172455", name: "Salmon, Atlantic, raw", canonical: "Salmo salar", category: "Protein", status: "pending-review", reviewer: { name: "Eve Nakamura", initials: "EN", role: "Dietitian" }, contributor: { name: "Sarah Chen", initials: "SC" }, comments: 2, updated: "2026-05-20", priority: "high", nutr: { calories: 208, p: 20.4, c: 0, f: 13.4 } },
{ id: "ing_172262", name: "Sweet Potato, cooked", canonical: "Ipomoea batatas", category: "Vegetable", status: "approved", reviewer: { name: "Aisha Bello", initials: "AB", role: "Manager" }, contributor: { name: "Sarah Chen", initials: "SC" }, comments: 0, updated: "2026-05-18", priority: "low", nutr: { calories: 86, p: 1.6, c: 20.1, f: 0.1 } },
{ id: "ing_170457", name: "Tomato, raw", canonical: "Solanum lycopersicum", category: "Vegetable", status: "approved", reviewer: { name: "Dana Liu", initials: "DL", role: "Compliance" }, contributor: { name: "Sarah Chen", initials: "SC" }, comments: 0, updated: "2026-06-02", priority: "low", nutr: { calories: 18, p: 0.9, c: 3.9, f: 0.2 } },
{ id: "ing_170393", name: "Carrot, raw", canonical: "Daucus carota", category: "Vegetable", status: "approved", reviewer: { name: "Aisha Bello", initials: "AB", role: "Manager" }, contributor: { name: "Sarah Chen", initials: "SC" }, comments: 0, updated: "2026-06-01", priority: "low", nutr: { calories: 41, p: 0.9, c: 9.6, f: 0.2 } },
{ id: "ing_169738", name: "Brown Rice, cooked", canonical: "Oryza sativa", category: "Grain", status: "approved", reviewer: { name: "Aisha Bello", initials: "AB", role: "Manager" }, contributor: { name: "Sarah Chen", initials: "SC" }, comments: 0, updated: "2026-05-30", priority: "low", nutr: { calories: 123, p: 2.7, c: 25.6, f: 1 } },
{ id: "ing_172421", name: "Lentils, cooked", canonical: "Lens culinaris", category: "Legume", status: "approved", reviewer: { name: "Dana Liu", initials: "DL", role: "Compliance" }, contributor: { name: "Sarah Chen", initials: "SC" }, comments: 0, updated: "2026-05-29", priority: "low", nutr: { calories: 116, p: 9, c: 20.1, f: 0.4 } },
{ id: "ing_173757", name: "Chickpeas, cooked", canonical: "Cicer arietinum", category: "Legume", status: "approved", reviewer: { name: "Dana Liu", initials: "DL", role: "Compliance" }, contributor: { name: "Sarah Chen", initials: "SC" }, comments: 0, updated: "2026-05-28", priority: "low", nutr: { calories: 164, p: 8.9, c: 27.4, f: 2.6 } },
{ id: "ing_172451", name: "Tofu, firm", canonical: "Glycine max", category: "Protein", status: "approved", reviewer: { name: "Eve Nakamura", initials: "EN", role: "Dietitian" }, contributor: { name: "Sarah Chen", initials: "SC" }, comments: 0, updated: "2026-05-27", priority: "low", nutr: { calories: 144, p: 17.3, c: 2.8, f: 8.7 } },
{ id: "ing_170379", name: "Broccoli, steamed", canonical: "Brassica oleracea", category: "Vegetable", status: "approved", reviewer: { name: "Aisha Bello", initials: "AB", role: "Manager" }, contributor: { name: "Sarah Chen", initials: "SC" }, comments: 0, updated: "2026-05-26", priority: "low", nutr: { calories: 35, p: 2.4, c: 7.2, f: 0.4 } },
{ id: "ing_172335", name: "Bell Pepper, red", canonical: "Capsicum annuum", category: "Vegetable", status: "approved", reviewer: { name: "Aisha Bello", initials: "AB", role: "Manager" }, contributor: { name: "Sarah Chen", initials: "SC" }, comments: 0, updated: "2026-05-24", priority: "low", nutr: { calories: 31, p: 1, c: 6, f: 0.3 } },
{ id: "ing_169291", name: "Oats, rolled, dry", canonical: "Avena sativa", category: "Grain", status: "approved", reviewer: { name: "Eve Nakamura", initials: "EN", role: "Dietitian" }, contributor: { name: "Sarah Chen", initials: "SC" }, comments: 0, updated: "2026-05-21", priority: "low", nutr: { calories: 389, p: 16.9, c: 66.3, f: 6.9 } },
{ id: "ing_172170", name: "Cucumber, raw", canonical: "Cucumis sativus", category: "Vegetable", status: "approved", reviewer: { name: "Aisha Bello", initials: "AB", role: "Manager" }, contributor: { name: "Sarah Chen", initials: "SC" }, comments: 0, updated: "2026-05-19", priority: "low", nutr: { calories: 15, p: 0.7, c: 3.6, f: 0.1 } },
{ id: "ing_171706", name: "Egg, whole", canonical: "Gallus gallus domesticus", category: "Protein", status: "approved", reviewer: { name: "Dana Liu", initials: "DL", role: "Compliance" }, contributor: { name: "Sarah Chen", initials: "SC" }, comments: 0, updated: "2026-05-17", priority: "low", nutr: { calories: 143, p: 12.6, c: 0.7, f: 9.5 } }];


if (typeof window !== "undefined") window.INGREDIENT_ITEMS = INGREDIENT_ITEMS;

function ingredientImageSrc(item) {
  if (!item) return "";
  const images = Array.isArray(item.images) ? item.images : [];
  const primary = images.find((image) => image && image.isPrimary) || images[0] || {};
  const candidate = item.image || item.image_url || item.cover || primary.url || primary.src || "";
  return typeof candidate === "string" ? candidate : (candidate.url || candidate.src || "");
}

function IngredientThumb({ item }) {
  const image = ingredientImageSrc(item);
  return (
    <div className="thumb sm" style={{ background: image ? "var(--gray-100)" : "linear-gradient(135deg, #DCEEC1, #C9E2A4)", display: "grid", placeItems: "center", color: "var(--green-700)", overflow: "hidden" }}>
      {image
        ? <img src={image} alt="" style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }}/>
        : <Icon name="leaf" size={18} stroke={1.8} />}
    </div>
  );
}

function IngredientsLibraryPage() {
  const { role, toast } = useApp();
  const [draftTick, bumpDrafts] = React.useState(0);
  const [binTick, setBinTick] = React.useState(0);
  React.useEffect(() => {
    const h = () => bumpDrafts((n) => n + 1);
    window.addEventListener("nutridms-drafts", h);
    const bh = () => setBinTick((n) => n + 1);
    window.addEventListener("nutridms-bin", bh);
    return () => { window.removeEventListener("nutridms-drafts", h); window.removeEventListener("nutridms-bin", bh); };
  }, []);
  const items = React.useMemo(() => {
    const binned = window.binnedIds ? window.binnedIds("ingredient") : new Set();
    return [
      ...((window.loadDraftItems ? window.loadDraftItems("ingredient") : [])),
      ...INGREDIENT_ITEMS,
    ].filter((i) => !binned.has(i.id));
  }, [draftTick, binTick]);
  const [confirmDel, setConfirmDel] = React.useState(null);
  const [view, setView] = React.useState("list");
  const [q, setQ] = React.useState("");
  const [status, setStatus] = React.useState(() => { const s = window.__libInitialStatus; window.__libInitialStatus = null; return s || "all"; });
  const [category, setCategory] = React.useState("all");
  const [sort, setSort] = React.useState("recent");
  const [selected, setSelected] = React.useState(new Set());
  const [menuFor, setMenuFor] = React.useState(null);
  const [advancedOpen, setAdvancedOpen] = React.useState(false);
  const [advPriority, setAdvPriority] = React.useState("all");
  const [advComments, setAdvComments] = React.useState(false);
  const [advReviewed, setAdvReviewed] = React.useState("all");
  // draft (uncommitted) modal values
  const [dStatus, setDStatus] = React.useState("all");
  const [dCategory, setDCategory] = React.useState("all");
  const [dSort, setDSort] = React.useState("recent");
  const [dPriority, setDPriority] = React.useState("all");
  const [dReviewed, setDReviewed] = React.useState("all");
  const openAdv = () => { setDStatus(status); setDCategory(category); setDSort(sort); setDPriority(advPriority); setDReviewed(advReviewed); setAdvancedOpen(true); };
  const applyAdv = () => { setStatus(dStatus); setCategory(dCategory); setSort(dSort); setAdvPriority(dPriority); setAdvReviewed(dReviewed); setAdvancedOpen(false); };

  React.useEffect(() => {
    const close = () => setMenuFor(null);
    if (menuFor) {document.addEventListener("click", close);return () => document.removeEventListener("click", close);}
  }, [menuFor]);

  const advActive = (advPriority !== "all" ? 1 : 0) + (advComments ? 1 : 0) + (advReviewed !== "all" ? 1 : 0);

  const filtered = React.useMemo(() => {
    // Nutrition Review owns every submitted/in-progress ingredient. The
    // Library never exposes pending, compliance-review or changes-requested
    // records, including to Admin and Super Admin.
    let list = [...items].filter((i) => {
      if (status === "draft") return i.status === "draft";
      if (status === "approved" || status === "published") return i.status === status;
      return ["approved", "published"].includes(i.status);
    });
    if (q) list = list.filter((i) => i.name.toLowerCase().includes(q.toLowerCase()) || i.canonical.toLowerCase().includes(q.toLowerCase()) || i.contributor.name.toLowerCase().includes(q.toLowerCase()));
    if (status !== "all") list = list.filter((i) => i.status === status);
    if (category !== "all") list = list.filter((i) => i.category === category);
    if (advPriority !== "all") list = list.filter((i) => i.priority === advPriority);
    if (advComments) list = list.filter((i) => i.comments > 0);
    if (advReviewed === "assigned") list = list.filter((i) => !!i.reviewer);
    if (advReviewed === "unassigned") list = list.filter((i) => !i.reviewer);
    if (sort === "az") list.sort((a, b) => a.name.localeCompare(b.name));
    if (sort === "calories") list.sort((a, b) => b.nutr.calories - a.nutr.calories);
    if (sort === "recent") list.sort((a, b) => b.updated.localeCompare(a.updated));
    return list;
  }, [q, status, category, sort, advPriority, advComments, advReviewed]);

  const categories = React.useMemo(() => Array.from(new Set(items.map((i) => i.category))).sort(), []);

  const isContributor = role === "media-contributor";
  const tabs = [
    { id: "all", label: "All ingredients", count: items.filter((i) => ["approved", "published"].includes(i.status)).length },
    { id: "approved", label: "Approved", count: items.filter((i) => i.status === "approved").length },
    { id: "published", label: "Published", count: items.filter((i) => i.status === "published").length },
    ...(isContributor ? [] : [{ id: "draft", label: "Drafts", count: items.filter((i) => i.status === "draft").length }])
  ];


  const toggle = (id) => {
    const next = new Set(selected);
    next.has(id) ? next.delete(id) : next.add(id);
    setSelected(next);
  };

  const setPage = window.__setPage || (() => {});
  const openIngredient = (i) => (window.__openIngredient || (() => {}))(i);
  const who = (window.currentUser ? (window.currentUser(role) || {}).name : null) || "You";
  const doBinDelete = () => {
    if (!confirmDel) return;
    const it = items.find((x) => x.id === confirmDel.id);
    if (it && window.binAdd) window.binAdd("ingredient", it, who);
    if (it && it.__remote && window.IngredientSync && window.IngredientSync.live()) window.IngredientSync.push(it, "delete");
    toast(`Moved “${confirmDel.name}” to Recycle Bin`);
    setConfirmDel(null);
  };

  return (
    <div className="ing-lib">
      <Crumbs path={[{ label: "Ingredients" }]} />
      <div className="rl-head">
        <div>
          <h1 className="rl-title">Ingredient Library</h1>
          <p className="rl-sub">Show all created ingredients alongside their status</p>
        </div>
        <div className="rl-head-actions">
          <button className="btn secondary"><Icon name="download" size={16} /> Export Data</button>
          {(typeof permAllowed === "function" ? permAllowed(role, "edit_ingredient") : false) && (
            <button className="btn secondary" onClick={() => setPage("edit-ingredient")}><Icon name="pencil" size={16} /> Edit Ingredient</button>
          )}
          <button className="btn primary" onClick={() => setPage("add-ingredient")}><Icon name="plus" size={16} /> Add Ingredient</button>
        </div>
      </div>

      <div className="rl-toolbar">
        <div className="rl-search">
          <Icon name="search" size={18} />
          <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search ingredient, contributor..." />
        </div>
        <div className="rl-tools">
          <div className="rl-viewtog">
            <button className={view === "grid" ? "on" : ""} onClick={() => setView("grid")} aria-label="Grid view"><Icon name="layout-grid" size={17} /></button>
            <button className={view === "list" ? "on" : ""} onClick={() => setView("list")} aria-label="List view"><Icon name="list" size={17} /></button>
          </div>
          <button className={`rl-advbtn ${advActive ? "on" : ""}`} onClick={openAdv}>
            <Icon name="sliders-horizontal" size={16} /> Advanced Filter{advActive ? ` · ${advActive}` : ""}
          </button>
        </div>
      </div>

      <div className="tabs rl-tabs">
        {tabs.map((t) =>
          <button key={t.id} className={status === t.id ? "on" : ""} onClick={() => setStatus(t.id)}>
            {t.label}
            <span className="rl-tab-count" style={{ background: status === t.id ? "var(--green-100)" : "var(--gray-100)", color: status === t.id ? "var(--green-700)" : "var(--gray-600)" }}>{t.count}</span>
          </button>
        )}
      </div>

      {selected.size > 0 &&
        <div style={{ display: "flex", gap: 8, alignItems: "center", marginBottom: 14 }}>
          <span className="muted" style={{ fontSize: 13, fontWeight: 600 }}>{selected.size} selected</span>
          <button className="btn sm secondary"><Icon name="check" size={14} /> Approve</button>
          <button className="btn sm secondary"><Icon name="archive" size={14} /> Archive</button>
        </div>
      }

      {view === "grid" ?
      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))", gap: 18 }}>
          {filtered.map((i) => <IngredientLibCard key={i.id} item={i} />)}
        </div> :

      <div className="lib-tablewrap">
          <div className="ing-tablewrap">
          <table className="table lib-table" style={{ minWidth: 880 }}>
            <thead>
              <tr>
                <th style={{ width: 36 }}>
                  <input type="checkbox" checked={selected.size === filtered.length && filtered.length > 0} onChange={(e) => setSelected(e.target.checked ? new Set(filtered.map((i) => i.id)) : new Set())} />
                </th>
                <th>Ingredient</th>
                <th>Category</th>
                <th>Reviewer</th>
                <th>Status</th>
                <th>Comments</th>
                <th>Updated</th>
                <th>Nutrition (per 100g)</th>
                <th style={{ width: 60 }}></th>
              </tr>
            </thead>
            <tbody>
              {filtered.map((i) =>
              <tr key={i.id} style={{ cursor: "pointer" }} onClick={() => openIngredient(i)}>
                  <td onClick={(e) => e.stopPropagation()}>
                    <input type="checkbox" checked={selected.has(i.id)} onChange={() => toggle(i.id)} />
                  </td>
                  <td>
                    <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
                      <IngredientThumb item={i}/>
                      <div>
                        <div style={{ fontWeight: 600, color: "var(--text-primary)", width: "210px", fontSize: "14px" }}>{i.name}</div>
                        <div style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 2 }}>
                          <span className="muted" style={{ fontSize: 12, fontStyle: "italic" }}>{i.canonical}</span>
                          <RefBadge kind="ingredient" item={i} />
                        </div>
                      </div>
                    </div>
                  </td>
                  <td>{i.category}</td>
                  <td>
                    {i.reviewer ?
                  <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                        <PersonAvatar person={i.reviewer} className="avatar sm" style={{ background: "var(--gray-100)", color: "var(--gray-700)" }} />
                        <div>
                          <div style={{ fontWeight: 600, fontSize: 13 }}>{i.reviewer.name}</div>
                          <div className="muted" style={{ fontSize: 11 }}>{i.reviewer.role}</div>
                        </div>
                      </div> :
                  <span className="muted" style={{ fontSize: 12, fontStyle: "italic" }}>Unassigned</span>}
                  </td>
                  <td><StatusPill status={i.status} item={i} kind="ingredient" /></td>
                  <td>
                    <span style={{ display: "inline-flex", alignItems: "center", gap: 4, fontSize: 13, color: "var(--gray-700)", fontWeight: 600 }}>
                      <Icon name="message-circle" size={13} stroke={2.4} /> {i.comments}
                    </span>
                  </td>
                  <td>{formatDate(i.updated)}</td>
                  <td>
                    <div className="muted" style={{ fontSize: 12 }}>
                      <strong style={{ color: "var(--text-primary)" }}>{i.nutr.calories} kcal</strong> · {i.nutr.p}p / {i.nutr.c}c / {i.nutr.f}f
                    </div>
                  </td>
                  <td onClick={(e) => e.stopPropagation()} style={{ position: "relative", overflow: "visible" }}>
                    <button className="icon-btn" title="Open menu" onClick={(e) => {e.stopPropagation();setMenuFor(menuFor === i.id ? null : i.id);}}><Icon name="more-horizontal" size={16} /></button>
                    {menuFor === i.id &&
                  <div className="card" style={{ position: "absolute", top: 38, right: 8, width: 188, zIndex: 32, padding: 6, boxShadow: "0 16px 40px -12px rgba(14,22,18,.3)" }} onClick={(e) => e.stopPropagation()}>
                        {[
                    { ic: "eye", label: "View details", fn: () => openIngredient(i) },
                    ...(i.status === "published" && !window.canEditPublished(role)
                      ? [{ ic: "lock", label: "Locked, published", locked: true, fn: () => toast("Published content is locked. Only an Admin can edit it.") }]
                      : [{ ic: "pencil", label: "Edit ingredient", fn: () => {window.__editIngredientId = i.id;setPage("edit-ingredient");} }]),
                    { ic: "copy", label: "Duplicate", fn: () => toast(`Duplicated “${i.name}”`) },
                    { ic: i.status === "approved" ? "rotate-ccw" : "check", label: i.status === "approved" ? "Send back to review" : "Approve", fn: () => toast(i.status === "approved" ? "Sent back to review" : `Approved “${i.name}”`) },
                    ...(i.status === "published" && !window.canEditPublished(role)
                      ? []
                      : [{ ic: "archive", label: "Archive", danger: false, fn: () => toast(`Archived “${i.name}”`) }]),
                    ...((role === "admin" || role === "super-admin") ? [{ ic: "trash-2", label: "Delete", danger: true, fn: () => setConfirmDel({ id: i.id, name: i.name }) }] : [])].
                    map((a, idx) =>
                    <button key={idx} className="menu-row" onClick={() => {setMenuFor(null);a.fn();}}
                    style={{ display: "flex", alignItems: "center", gap: 10, width: "100%", padding: "8px 10px", border: 0, background: "transparent", borderRadius: 8, fontSize: 13, fontWeight: 600, textAlign: "left", color: a.danger ? "var(--error-600)" : a.locked ? "var(--gray-500)" : "var(--text-primary)", cursor: "pointer" }}
                    onMouseEnter={(e) => e.currentTarget.style.background = a.danger ? "var(--error-50)" : "var(--gray-50)"}
                    onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}>
                            <Icon name={a.ic} size={15} stroke={2.2} /> {a.label}
                          </button>
                    )}
                      </div>
                  }
                  </td>
                </tr>
              )}
            </tbody>
          </table>
          </div>
          {filtered.length === 0 &&
        <div className="empty">
              <div className="icon"><Icon name={items.length ? "search-x" : "leaf"} size={24} /></div>
              <h3>{items.length ? "No ingredients match your filters" : "Your ingredient library is empty"}</h3>
              <p>{items.length ? "Try clearing the search or switching tabs." : "Add your first ingredient or import a catalogue to start building your governed nutrition library."}</p>
              {!items.length && (
                <div style={{ display: "flex", justifyContent: "center", gap: 10, marginTop: 18, flexWrap: "wrap" }}>
                  <button className="btn primary" onClick={() => setPage("add-ingredient")}><Icon name="plus" size={15} /> Add first ingredient</button>
                  <button className="btn secondary" onClick={() => { window.__settingsTab = "imports"; setPage("settings"); }}><Icon name="upload-cloud" size={15} /> Import catalogue</button>
                </div>
              )}
            </div>
        }
        </div>
      }

      {filtered.length > 0 &&
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 18, color: "var(--gray-600)", fontSize: 13 }}>
          <span>Showing 1–{filtered.length} of {filtered.length}</span>
          <div style={{ display: "flex", gap: 6 }}>
            <button className="btn secondary sm" disabled><Icon name="chevron-left" size={14} /> Previous</button>
            <button className="btn secondary sm" disabled>Next <Icon name="chevron-right" size={14} /></button>
          </div>
        </div>
      }

      {advancedOpen && (
        <div className="rl-modal-scrim" onClick={() => setAdvancedOpen(false)}>
          <div className="rl-modal" onClick={(e) => e.stopPropagation()} role="dialog" aria-modal="true">
            <button className="rl-modal-x" onClick={() => setAdvancedOpen(false)} aria-label="Close"><Icon name="x" size={18} /></button>
            <h2 className="rl-modal-t">Advanced Filters</h2>
            <p className="rl-modal-sub">Filter ingredients by these options</p>
            <div className="rl-modal-grid">
              <label className="rl-mfield">
                <span>Status</span>
                <select value={dStatus} onChange={(e) => setDStatus(e.target.value)}>
                  <option value="all">Select status</option>
                  <option value="published">Published</option>
                  <option value="rejected">Rejected</option>
                </select>
              </label>
              <label className="rl-mfield">
                <span>Category</span>
                <select value={dCategory} onChange={(e) => setDCategory(e.target.value)}>
                  <option value="all">All categories</option>
                  {categories.map((c) => <option key={c} value={c}>{c}</option>)}
                </select>
              </label>
              <label className="rl-mfield">
                <span>Priority</span>
                <select value={dPriority} onChange={(e) => setDPriority(e.target.value)}>
                  <option value="all">Any priority</option>
                  <option value="high">High</option>
                  <option value="medium">Medium</option>
                  <option value="low">Low</option>
                </select>
              </label>
              <label className="rl-mfield">
                <span>Reviewer</span>
                <select value={dReviewed} onChange={(e) => setDReviewed(e.target.value)}>
                  <option value="all">Any reviewer</option>
                  <option value="assigned">Assigned</option>
                  <option value="unassigned">Unassigned</option>
                </select>
              </label>
              <label className="rl-mfield">
                <span>Sort by</span>
                <select value={dSort} onChange={(e) => setDSort(e.target.value)}>
                  <option value="recent">Recently updated</option>
                  <option value="az">Name (A–Z)</option>
                  <option value="calories">Calories (high → low)</option>
                </select>
              </label>
            </div>
            <div className="rl-modal-foot">
              <button className="btn secondary" onClick={() => setAdvancedOpen(false)}>Cancel</button>
              <button className="btn primary" onClick={applyAdv}><Icon name="filter" size={15} /> Apply Filter</button>
            </div>
          </div>
        </div>
      )}

      {confirmDel && <ConfirmDialog
        title={`Move “${confirmDel.name}” to Recycle Bin?`}
        body={`Deleted items are kept in the Recycle Bin for ${window.BIN_RETENTION_DAYS || 90} days, then permanently removed. An Admin can restore or permanently delete them from Settings → Recycle Bin.`}
        confirmLabel="Move to Recycle Bin" tone="danger" icon="trash-2"
        onCancel={() => setConfirmDel(null)} onConfirm={doBinDelete} />}
    </div>);

}

function IngredientLibCard({ item }) {
  const image = ingredientImageSrc(item);
  return (
    <div className="card" style={{ overflow: "hidden", cursor: "pointer", display: "flex", flexDirection: "column", transition: "border-color .12s ease, box-shadow .12s ease" }}
    onClick={() => (window.__openIngredient || (() => {}))(item)}
    onMouseEnter={(e) => {e.currentTarget.style.borderColor = "var(--green-300)";e.currentTarget.style.boxShadow = "0 6px 16px -8px rgba(14,22,18,.1)";}}
    onMouseLeave={(e) => {e.currentTarget.style.borderColor = "var(--gray-200)";e.currentTarget.style.boxShadow = "none";}}>
      <div style={{ height: 140, background: image ? "var(--gray-100)" : "linear-gradient(135deg, #DCEEC1, #C9E2A4)", display: "grid", placeItems: "center", color: "var(--green-700)", overflow: "hidden" }}>
        {image
          ? <img src={image} alt={item.name || "Ingredient"} style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }}/>
          : <Icon name="leaf" size={40} stroke={1.6} />}
      </div>
      <div style={{ padding: 14, display: "flex", flexDirection: "column", gap: 8, flex: 1 }}>
        <div>
          <div style={{ fontWeight: 700, fontSize: 14, color: "var(--text-primary)" }}>{item.name}</div>
          <div style={{ fontSize: 11, color: "var(--gray-500)", fontStyle: "italic" }}>{item.canonical}</div>
        </div>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
          <span className="tag" style={{ background: "var(--green-50)", color: "var(--green-700)", border: "1px solid var(--green-200)", fontSize: 11 }}>{item.category}</span>
          <StatusPill status={item.status} />
        </div>
        <div style={{ fontSize: 11.5, color: "var(--gray-600)" }}>
          <strong style={{ color: "var(--text-primary)" }}>{item.nutr.calories} kcal</strong> · {item.nutr.p}p / {item.nutr.c}c / {item.nutr.f}f
        </div>
        <div style={{ marginTop: "auto", display: "flex", alignItems: "center", justifyContent: "space-between", paddingTop: 10, borderTop: "1px solid var(--gray-100)" }}>
          {item.reviewer ?
          <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
              <PersonAvatar person={item.reviewer} className="avatar sm" style={{ background: "var(--gray-100)", color: "var(--gray-700)", fontSize: 10 }} />
              <div style={{ fontSize: 11, color: "var(--gray-600)" }}>{item.reviewer.name}</div>
            </div> :
          <span style={{ fontSize: 11, color: "var(--gray-400)", fontStyle: "italic" }}>Unassigned</span>}
          <span style={{ display: "inline-flex", alignItems: "center", gap: 4, fontSize: 11, color: "var(--gray-600)" }}>
            <Icon name="message-circle" size={11} stroke={2.4} /> {item.comments}
          </span>
        </div>
      </div>
    </div>);

}

/* Nutrient education — what each nutrient is, why it matters, who it affects. */
const NUTRIENT_EDU = {
  "Calories": { what: "The total energy the ingredient provides, measured in kilocalories (kcal) per 100g.", why: "Energy balance drives weight management and daily intake targets.", who: "Central for weight-loss, weight-gain, athletic and paediatric programs." },
  "Protein": { what: "Amino-acid building blocks used to build and repair tissue.", why: "Supports muscle maintenance, satiety, immune function and recovery.", who: "Key for athletes, older adults, and high-protein or renal-controlled diets." },
  "Carbohydrates": { what: "The body's primary quick energy source, including starches and sugars.", why: "Fuels the brain and muscles; type and amount affect blood glucose.", who: "Closely managed for diabetes, low-carb and endurance programs." },
  "Total fat": { what: "Concentrated energy plus carriers for fat-soluble vitamins.", why: "Quality (unsaturated vs saturated) matters more than quantity for heart health.", who: "Watched in heart-health, weight and gallbladder-sensitive diets." },
  "Fibre": { what: "Indigestible plant carbohydrate that adds bulk to the diet.", why: "Aids digestion, feeds gut bacteria, and steadies blood sugar and cholesterol.", who: "Important for gut health, diabetes, and heart-health programs." },
  "Sugar": { what: "Simple carbohydrates, both naturally occurring and added.", why: "High added sugar spikes blood glucose and adds empty calories.", who: "Restricted for diabetes, obesity and dental-health guidance." },
  "Saturated": { what: "Fat that is solid at room temperature, mainly from animal products.", why: "High intake can raise LDL ('bad') cholesterol.", who: "Limited in heart-health and high-cholesterol diets." },
  "Monounsaturated": { what: "A heart-friendly unsaturated fat (e.g. olive oil, avocado).", why: "Associated with improved cholesterol profiles.", who: "Encouraged in Mediterranean and heart-health plans." },
  "Polyunsaturated": { what: "Unsaturated fats including omega-3 and omega-6.", why: "Essential fats the body cannot make; support heart and brain health.", who: "Prioritised in heart-health and cognitive-support diets." },
  "Trans fat": { what: "Industrially processed fat, largely phased out of food supply.", why: "Raises bad cholesterol and lowers good — the least healthy fat.", who: "Avoided across all diets; flagged in every compliance review." },
  "Cholesterol": { what: "A waxy substance found in animal foods.", why: "Dietary cholesterol has a modest effect on blood cholesterol for most people.", who: "Monitored in heart-health and familial-hypercholesterolaemia guidance." },
  "Omega-3": { what: "An essential polyunsaturated fat from fish and some seeds.", why: "Anti-inflammatory; supports heart, brain and eye health.", who: "Emphasised in heart-health, pregnancy and cognitive programs." },
  "Sodium": { what: "A mineral, mostly from salt, that regulates fluid balance.", why: "Excess sodium raises blood pressure and cardiovascular risk.", who: "Tightly limited for hypertension, heart and kidney (CKD) diets." },
  "Potassium": { what: "A mineral that balances sodium and supports nerve and muscle function.", why: "Helps lower blood pressure; must be controlled in advanced kidney disease.", who: "Increased for hypertension; restricted in some CKD stages." },
  "Calcium": { what: "The main mineral in bones and teeth.", why: "Essential for bone strength, muscle contraction and nerve signalling.", who: "Prioritised in pregnancy, paediatric, and osteoporosis-risk diets." },
  "Iron": { what: "A mineral needed to carry oxygen in the blood.", why: "Deficiency causes anaemia and fatigue; vitamin C aids absorption.", who: "Critical for anaemia, pregnancy, and vegetarian programs." },
  "Magnesium": { what: "A mineral involved in hundreds of enzyme reactions.", why: "Supports muscle, nerve, and energy metabolism.", who: "Relevant to cardiovascular and metabolic health." },
  "Zinc": { what: "A trace mineral for immunity and wound healing.", why: "Supports immune response, growth and taste.", who: "Monitored in paediatric and immune-support diets." },
  "Vitamin C": { what: "A water-soluble antioxidant vitamin.", why: "Supports immunity, collagen synthesis and iron absorption.", who: "Paired with iron for anaemia; general immune support." },
  "Vitamin A": { what: "A fat-soluble vitamin important for vision and skin.", why: "Supports eyesight, immune function and cell growth.", who: "Managed carefully in pregnancy (upper limits apply)." },
  "Vitamin D": { what: "The 'sunshine' vitamin that regulates calcium.", why: "Essential for bone health and immune function.", who: "Prioritised in bone-health, paediatric and older-adult diets." },
};

/* Richer explanation layer — daily value, food sources, deficiency/excess,
   and compliance relevance. Merged onto NUTRIENT_EDU so both the recipe and
   ingredient drawers render a deep, clinical explanation for every nutrient. */
const NUTRIENT_RICH = {
  "Calories": { dv: "2,000 kcal reference intake / day", sources: "All macronutrients — fat (9/g), carbs & protein (4/g), alcohol (7/g)", low: "Under-eating drives fatigue, muscle loss and poor recovery.", high: "Chronic surplus drives weight gain and metabolic risk.", policy: "Meal-program calorie ceilings and per-serving caps are enforced per health-condition rule." },
  "Protein": { dv: "50 g / day (0.8 g per kg body weight)", sources: "Poultry, fish, eggs, dairy, legumes, tofu, quinoa", low: "Deficiency causes muscle wasting, weak immunity, slow healing.", high: "Very high intake may strain kidneys in renal patients.", policy: "Minimum-protein floors apply to weight-management & clinical builds." },
  "Carbohydrates": { dv: "275 g / day", sources: "Grains, fruit, starchy vegetables, legumes", low: "Too low can cause fatigue, brain fog and ketosis.", high: "Excess refined carbs spike glucose and add empty calories.", policy: "Carb ceilings are central to diabetic and low-carb rule sets." },
  "Total fat": { dv: "78 g / day", sources: "Oils, nuts, seeds, dairy, fatty fish, avocado", low: "Too little impairs vitamin absorption and hormones.", high: "Excess adds calories; saturated share matters most.", policy: "Total & saturated fat caps enforced for heart-health programs." },
  "Fibre": { dv: "28 g / day", sources: "Whole grains, legumes, vegetables, fruit, nuts", low: "Low fibre causes constipation and poor glycaemic control.", high: "Very high intake without water can cause bloating.", policy: "Minimum-fibre targets encouraged in gut- and heart-health rules." },
  "Sugar": { dv: "Added sugar < 50 g / day (ideally < 25 g)", sources: "Fruit (natural), sweeteners, syrups, confectionery", low: "No deficiency risk — the body makes glucose as needed.", high: "Excess drives glucose spikes, weight gain, dental decay.", policy: "Added-sugar limits are hard caps in diabetic & paediatric rules." },
  "Saturated": { dv: "< 20 g / day", sources: "Butter, fatty meat, coconut/palm oil, full-fat dairy", low: "No deficiency concern.", high: "Raises LDL cholesterol and cardiovascular risk.", policy: "A blocking threshold in most heart-health rule sets." },
  "Sodium": { dv: "< 2,300 mg / day (≈ 1 tsp salt)", sources: "Salt, cured meats, sauces, processed foods, bread", low: "Rare; can affect athletes with heavy sweat loss.", high: "Raises blood pressure and cardiovascular/kidney risk.", policy: "Tight sodium ceilings enforced for hypertension & renal rules." },
  "Potassium": { dv: "3,400 mg / day (men), 2,600 mg (women)", sources: "Banana, potato, beans, leafy greens, dairy", low: "Causes weakness, cramps and arrhythmia.", high: "Dangerous in advanced kidney disease (hyperkalaemia).", policy: "Both floors and ceilings apply depending on CKD stage." },
  "Calcium": { dv: "1,000–1,300 mg / day", sources: "Dairy, fortified plant milks, tofu, leafy greens", low: "Weakens bones; raises osteoporosis risk.", high: "Very high intake may cause kidney stones.", policy: "Minimum targets prioritised in pregnancy & paediatric rules." },
  "Iron": { dv: "8 mg (men) / 18 mg (women) per day", sources: "Red meat, liver, legumes, spinach, fortified cereal", low: "Deficiency causes anaemia, fatigue, pallor.", high: "Excess is toxic; caution with supplements.", policy: "Iron floors emphasised in anaemia & pregnancy programs." },
  "Vitamin C": { dv: "90 mg (men) / 75 mg (women) per day", sources: "Citrus, berries, peppers, broccoli, kiwi", low: "Deficiency causes scurvy, poor healing.", high: "Excess is excreted; very high doses upset the gut.", policy: "Paired with iron rules to boost absorption." },
  "Vitamin A": { dv: "900 µg (men) / 700 µg (women) per day", sources: "Liver, carrots, sweet potato, leafy greens, egg", low: "Deficiency impairs vision and immunity.", high: "Excess (esp. retinol) is teratogenic in pregnancy.", policy: "Upper limits strictly enforced in pregnancy rules." },
  "Vitamin D": { dv: "15–20 µg (600–800 IU) per day", sources: "Oily fish, fortified milk, egg yolk, sunlight", low: "Deficiency weakens bones (rickets/osteomalacia).", high: "Excess causes calcium build-up and kidney issues.", policy: "Floors prioritised in bone-health & older-adult rules." },
};
Object.keys(NUTRIENT_RICH).forEach((k) => { if (NUTRIENT_EDU[k]) Object.assign(NUTRIENT_EDU[k], NUTRIENT_RICH[k]); });

if (typeof window !== "undefined") window.NUTRIENT_EDU = NUTRIENT_EDU;

/* Ingredient Detail, mirrors the recipe-detail layout */
function IngredientDetail() {
  const { activeIngredient, setActiveIngredient, role, setPage, toast, detailFocus, setDetailFocus, openQuickAssign } = useApp();
  const ID_SEC_TAB = { "Macronutrients": "macros", "Fatty Acids": "fatty-acids", "Micronutrients": "micros", "Allergens": "allergens", "Nutrition": "nutrition", "Sources": "sourcing", "Basics": "overview" };
  const idFocus = detailFocus && detailFocus.kind === "ingredient" ? detailFocus : null;
  const [tab, setTab] = aUseState(idFocus ? ID_SEC_TAB[idFocus.sec] || "overview" : "overview");
  const [idFlag, setIdFlag] = aUseState(idFocus || null);
  aUseEffect(() => {if (idFocus) setDetailFocus(null);}, []);
  const [comment, setComment] = aUseState("");
  const [modal, setModal] = aUseState(null);
  const [resolved, setResolved] = aUseState(false);
  const [urgentTo, setUrgentTo] = aUseState(null);
  const [urgentNote, setUrgentNote] = aUseState("");
  const [nutrInfo, setNutrInfo] = aUseState(null);
  const [stockOpen, setStockOpen] = aUseState(false);
  const [invModal, setInvModal] = aUseState(false);
  const [canonicalOptions, setCanonicalOptions] = aUseState([]);
  const [canonicalLoading, setCanonicalLoading] = aUseState(false);
  const [canonicalChoice, setCanonicalChoice] = aUseState("");
  const [canonicalSaving, setCanonicalSaving] = aUseState(false);
  const [canonicalError, setCanonicalError] = aUseState("");
  aUseEffect(() => {
    const item = activeIngredient;
    const member = window.__nutridmsAuthenticatedUser || {};
    const editable = item && ["draft", "pending-review", "changes-requested"].includes(item.status);
    const canMap = !!(item && item.__remote && editable && Array.isArray(member.effectivePermissions) && member.effectivePermissions.includes("ingredients:map"));
    setCanonicalChoice(item && item.canonicalId ? String(item.canonicalId) : "");
    setCanonicalError("");
    if (!canMap || !window.NutriIngredients || typeof window.NutriIngredients.canonical !== "function") {
      setCanonicalOptions([]);
      setCanonicalLoading(false);
      return undefined;
    }
    let cancelled = false;
    setCanonicalLoading(true);
    window.NutriIngredients.canonical({ page_size: 100 }).then((response) => {
      if (!cancelled) setCanonicalOptions(Array.isArray(response) ? response : ((response && response.results) || []));
    }).catch(() => {
      if (!cancelled) setCanonicalOptions([]);
    }).finally(() => { if (!cancelled) setCanonicalLoading(false); });
    return () => { cancelled = true; };
  }, [activeIngredient && activeIngredient.id, activeIngredient && activeIngredient.status, activeIngredient && activeIngredient.canonicalId]);
  if (!activeIngredient) return null;
  const i = activeIngredient;
  const hasChangeRequest = i.status === "changes-requested" || i.status === "compliance-review";

  // reuse the recipe-detail helpers (exported to window)
  const Timeline = window.Timeline,KeyValue = window.KeyValue,CalorieRing = window.CalorieRing,CompletionBar = window.CompletionBar;

  const canReview = ["reviewer", "compliance", "manager", "admin", "super-admin"].includes(role);
  const canFinalize = ["compliance", "admin", "super-admin"].includes(role);
  const me = currentUser(role);
  const urgentPeople = (URGENT_ESCALATION_BY_ROLE[role] || []).flatMap((rl) => usersByRole(rl));

  // Workflow-aware actions driven by the org's saved Publishing Workflow
  const flow = window.weFlow ? window.weFlow(i, "ingredient", role) : null;
  const canRequestChanges = flow && i.__remote
    ? !!(flow.actions && flow.actions.can_request_changes)
    : canReview;
  const canEditPub = window.canEditPublished ? window.canEditPublished(role) : (role === "admin" || role === "super-admin");
  const sessionMember = window.__nutridmsAuthenticatedUser || {};
  const canMapCanonical = !!(
    i.__remote
    && ["draft", "pending-review", "changes-requested"].includes(i.status)
    && Array.isArray(sessionMember.effectivePermissions)
    && sessionMember.effectivePermissions.includes("ingredients:map")
  );
  const saveCanonicalMapping = async () => {
    if (!canMapCanonical || !canonicalChoice || canonicalSaving) return;
    setCanonicalSaving(true);
    setCanonicalError("");
    try {
      await window.NutriIngredients.map(i.id, canonicalChoice, 1, "human");
      if (window.IngredientSync && typeof window.IngredientSync.sync === "function") await window.IngredientSync.sync();
      const refreshed = (window.INGREDIENT_ITEMS || []).find((item) => String(item.id) === String(i.id));
      if (refreshed) setActiveIngredient({ ...refreshed });
      toast("Canonical mapping saved");
    } catch (error) {
      setCanonicalError((error && (error.message || error.detail)) || "The canonical mapping could not be saved.");
    } finally {
      setCanonicalSaving(false);
    }
  };
  const requestChanges = () => openQuickAssign && openQuickAssign({ kind: "ingredient", item: i, reason: "changes" });
  const doPrimary = () => {
    if (!flow || !flow.primary) return;
    if (flow.primary.kind === "view") { window.__libInitialStatus = "published"; setActiveIngredient(null); setPage("ingredients"); return; }
    setModal("complete");
  };
  const confirmComplete = async () => {
    try {
      await window.weServerAction("ingredient", i, flow);
      const refreshed = (window.INGREDIENT_ITEMS || []).find(item => String(item.id) === String(i.id));
      if (refreshed) setActiveIngredient({ ...refreshed });
      setModal(null);
      toast(flow && flow.isFinal ? "Approval recorded by NutriDMS" : "Review stage completed by NutriDMS");
    } catch (error) {
      toast((error && (error.message || error.detail)) || "The workflow action could not be completed.");
    }
  };
  const wfActions = flow ? (() => {
    const a = [];
    const isPub = i.status === "published";
    if (!isPub || canEditPub)
      a.push({ id: "edit", label: isPub ? "Edit (Admin)" : "Edit", icon: "pencil", onClick: () => { window.__editIngredientId = i.id; setPage("edit-ingredient"); } });
    if (canRequestChanges && i.status !== "approved" && !isPub) {
      a.push({ id: "request-changes", label: "Request Changes", icon: "message-square", onClick: requestChanges });
      if (can(role, "mark_urgent") && i.status !== "rejected")
        a.push({ id: "urgent", label: "Urgent review", icon: "alert-triangle", style: { borderColor: "#FEDF89", color: "#b54708" }, onClick: () => { setUrgentTo(urgentPeople[0] ? urgentPeople[0].initials : null); setUrgentNote(""); setModal("urgent"); } });
      if (can(role, "reject") && i.status !== "rejected")
        a.push({ id: "reject", label: "Reject", icon: "x", tone: "danger", onClick: () => setModal("reject") });
    }
    if (isPub && canEditPub)
      a.push({ id: "delete", label: "Delete", icon: "trash-2", tone: "danger", onClick: () => setModal("delete") });
    return a;
  })() : [];
  const wfPrimary = flow && flow.primary && (flow.canActPrimary || flow.primary.kind === "view")
    ? { label: flow.primary.label, icon: flow.primary.icon, onClick: doPrimary }
    : null;

  const usedIn = [
  { n: "Mediterranean Quinoa Bowl", id: "R-001" },
  { n: "Power Breakfast Bowl", id: "R-014" },
  { n: "Charred Broccolini with Tahini", id: "R-006" }];

  const compliance = [
  { t: "USDA FoodData Central match", ok: true },
  { t: "Allergen profile declared", ok: i.category !== "Nut" },
  { t: "Source documentation attached", ok: i.status === "approved" },
  { t: "Nutrition verified by dietitian", ok: !!i.reviewer }];

  const macros = [
  { key: "Protein", value: i.nutr.p, total: 50, color: "var(--green-700)" },
  { key: "Carbs", value: i.nutr.c, total: 100, color: "var(--warning-500)" },
  { key: "Fat", value: i.nutr.f, total: 70, color: "#6938EF" }];


  return (
    <div>
      <Crumbs path={[
      { label: "Ingredient Library", onClick: () => {setActiveIngredient(null);setPage("ingredients");} },
      { label: i.name }]
      } />

      <div className="page-head" style={{ alignItems: "flex-start" }}>
        <div style={{ minWidth: 0 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 8, flexWrap: "wrap" }}>
            <StatusPill status={i.status} item={i} kind="ingredient" />
            <PriorityPill priority={i.priority} />
            <RefBadge kind="ingredient" item={i} size="md" />
          </div>
          <h1 className="page-title">{i.name}</h1>
          <p className="page-sub" style={{ maxWidth: 720, fontStyle: "italic" }}>{i.canonical} · {i.category}</p>
        </div>
        <div className="rd-head-actions">
          <button className="btn secondary" onClick={() => { const p = window.__prevPage; setActiveIngredient(null); setPage(p && p !== "ingredient-detail" ? p : "ingredients"); }}><Icon name="arrow-left" size={16} /> Back</button>
          {flow ? (
            <WfActionBar flow={flow} actions={wfActions} primary={wfPrimary} />
          ) : (
            <button className="btn secondary" onClick={() => {window.__editIngredientId = i.id;setPage("edit-ingredient");}}><Icon name="pencil" size={16} /> Edit</button>
          )}
        </div>
      </div>

      <WorkflowBanner flow={flow} item={{ name: i.name, nutr: i.nutr || {}, ingredients: [{ name: i.name }], allergens: i.allergens || [] }} kind="ingredient" />
      <div className="card pad" style={{ marginTop: 16 }}>
        <div style={{ display: "flex", alignItems: "flex-start", gap: 10 }}>
          <Icon name={i.canonicalId ? "git-merge" : "circle-dot"} size={18} color={i.canonicalId ? "var(--green-700)" : "var(--gray-500)"} />
          <div style={{ minWidth: 0, flex: 1 }}>
            <h3 style={{ fontFamily: "var(--serif)", fontSize: 18, margin: 0 }}>Canonical mapping</h3>
            {i.canonicalId
              ? <p className="muted" style={{ margin: "4px 0 0" }}>Mapped to <strong>{i.canonical}</strong>. This enriches traceability and is not an approval requirement.</p>
              : <p className="muted" style={{ margin: "4px 0 0" }}>Not mapped — optional. Review and approval can continue without a canonical ingredient.</p>}
          </div>
        </div>
        {canMapCanonical && (
          <div style={{ marginTop: 14 }}>
            {canonicalLoading ? <span className="muted"><Icon name="loader" size={14} className="spin" /> Loading the canonical catalog…</span> : canonicalOptions.length ? <>
              <label className="muted" style={{ display: "block", fontSize: 12, fontWeight: 700, marginBottom: 6 }}>Optional mapping</label>
              <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
                <select value={canonicalChoice} onChange={(event) => setCanonicalChoice(event.target.value)} style={{ minWidth: 260, flex: "1 1 260px" }}>
                  <option value="">Choose a canonical ingredient…</option>
                  {canonicalChoice && i.canonicalId && !canonicalOptions.some((option) => String(option.id) === String(canonicalChoice)) && <option value={canonicalChoice}>{i.canonical}</option>}
                  {canonicalOptions.map((option) => <option key={option.id} value={option.id}>{option.name}{option.cas ? " · " + option.cas : ""}{option.verified ? " · Verified" : ""}</option>)}
                </select>
                <button className="btn secondary" disabled={!canonicalChoice || String(canonicalChoice) === String(i.canonicalId || "") || canonicalSaving} onClick={saveCanonicalMapping}>
                  <Icon name="git-merge" size={14} /> {canonicalSaving ? "Saving…" : i.canonicalId ? "Change mapping" : "Map ingredient"}
                </button>
              </div>
            </> : <p className="muted" style={{ margin: "12px 0 0" }}>This organization has no canonical ingredient catalog yet. Approval remains available.</p>}
            {canonicalError && <p className="error" role="alert" style={{ margin: "10px 0 0" }}>{canonicalError}</p>}
          </div>
        )}
      </div>
      {(!flow || flow.phase === "draft") && window.WeComplianceBadge && (
        <div className="id-orgbadge-row">
          <span className="id-orgbadge-lbl"><Icon name="list-checks" size={13} stroke={2.4} /> Organization compliance</span>
          <window.WeComplianceBadge item={{ name: i.name, nutr: i.nutr || {}, ingredients: [{ name: i.name }], allergens: i.allergens || [] }} kind="ingredient" />
        </div>
      )}
      {i.status === "published" && <PublishedLock canEdit={canEditPub} />}

      {(() => {
        var st = null; try { st = window.NutriInvLink && window.NutriInvLink.ingredientStock(i); } catch (e) {}
        if (!st) return null;
        var tone = st.status === "out" ? "block" : st.status === "low" ? "warn" : "ok";
        var label = st.status === "out" ? "Out of stock" : st.status === "low" ? "Below reorder point" : "In stock";
        return (
          <div className={"id-stock-card id-stock-" + tone + (stockOpen ? " open" : " collapsed")}>
            <button className="id-stock-h id-stock-toggle" onClick={() => setStockOpen((v) => !v)}>
              <Icon name="package" size={15} stroke={2.2} /> Inventory <span className={"id-stock-badge " + tone}>{label}</span>
              {!stockOpen && <span className="id-stock-peek">{st.onHand} {st.unit} · ${st.value.toLocaleString()}</span>}
              <Icon name={stockOpen ? "chevron-up" : "chevron-down"} size={16} stroke={2.4} className="id-stock-chev" />
            </button>
            {stockOpen && (
            <React.Fragment>
            <div className="id-stock-grid">
              <div><span>On hand</span><b>{st.onHand} {st.unit}</b></div>
              <div><span>Available</span><b>{st.available} {st.unit}</b></div>
              <div><span>Reorder point</span><b>{st.reorderPoint || "—"} {st.reorderPoint ? st.unit : ""}</b></div>
              <div><span>Unit cost</span><b>${st.unitCost}/{st.unit}</b></div>
              <div><span>Stock value</span><b>${st.value.toLocaleString()}</b></div>
            </div>
            <div className="id-stock-actions">
              <button className="btn secondary sm" onClick={() => setInvModal(true)}><Icon name="boxes" size={14} /> View in inventory</button>
              {st.status !== "ok" && <button className="btn primary sm" onClick={() => { try { var po = window.NutriInvLink.draftPOForItem(st.itemId); window.__setPage && window.__setPage("inv-po"); toast && toast(po ? "Draft PO created for " + st.name : "Add an approved supplier first"); } catch (e) {} }}><Icon name="clipboard-list" size={14} /> Reorder → draft PO</button>}
            </div>
            {(() => {
              const est = window.NutriInvLink && window.NutriInvLink.estimatePrice(st.unitCost, "product");
              if (!est) return null;
              return (
                <div className="id-stock-sim">
                  <div className="id-stock-sim-h"><Icon name="calculator" size={13} stroke={2.2} /> Value simulation <span className="muted" style={{ marginLeft: "auto", fontSize: 11 }}>per {st.unit} · product margin</span></div>
                  <div className="id-stock-sim-grid">
                    <div><span>Unit cost</span><b>${st.unitCost}</b></div>
                    <div><span>Est. price {est.single ? "(" + est.marginLow + "%)" : "(" + est.marginLow + "%)"}</span><b>${est.low}</b></div>
                    {!est.single && <div><span>Est. price ({est.marginHigh}%)</span><b>${est.high}</b></div>}
                    <div className="ok"><span>Est. margin/{st.unit}</span><b>${Math.round((est.mid - st.unitCost) * 100) / 100}</b></div>
                  </div>
                </div>
              );
            })()}
            </React.Fragment>
            )}
            {invModal && ReactDOM.createPortal(
              <div className="inv-scrim id-invpop-scrim" onMouseDown={(e) => e.target === e.currentTarget && setInvModal(false)}>
                <div className="id-invpop">
                  <div className="id-invpop-h"><b><Icon name="package" size={16} stroke={2.2} /> {st.name} — inventory</b><button className="inv-x" onClick={() => setInvModal(false)}><Icon name="x" size={18} /></button></div>
                  <div className="id-invpop-b">
                    <div className="id-invpop-kpis">
                      <div><span>On hand</span><b>{st.onHand} {st.unit}</b></div>
                      <div><span>Available</span><b>{st.available} {st.unit}</b></div>
                      <div><span>Reorder point</span><b>{st.reorderPoint || "—"} {st.reorderPoint ? st.unit : ""}</b></div>
                      <div><span>Unit cost</span><b>${st.unitCost}/{st.unit}</b></div>
                      <div><span>Stock value</span><b>${st.value.toLocaleString()}</b></div>
                      <div><span>Status</span><b className={tone === "ok" ? "" : tone}>{label}</b></div>
                    </div>
                    {(() => {
                      const INV = window.NutriInventory;
                      const lots = INV && st.itemId ? INV.lots().filter((l) => l.itemId === st.itemId && INV.lotBalance(l.id) > 0) : [];
                      if (!lots.length) return <div className="id-invpop-empty"><Icon name="layers" size={22} /><p>No active lots for this item.</p></div>;
                      return (
                        <div className="inv-table-wrap"><table className="inv-table">
                          <thead><tr><th>Lot</th><th>Balance</th><th>Warehouse</th><th>Expiry</th><th>Status</th></tr></thead>
                          <tbody>{lots.map((l) => <tr key={l.id}><td className="inv-mono">{l.internalLot || l.supplierLot || l.id}</td><td>{INV.lotBalance(l.id)} {l.unit}</td><td className="inv-muted">{(INV.warehouses().find((w) => w.id === l.warehouseId) || {}).name || l.warehouseId || "—"}</td><td className="inv-muted">{l.expiresAt ? new Date(l.expiresAt).toLocaleDateString() : "—"}</td><td><span className={"inv-pill " + (l.status === "available" ? "ok" : "warn")}>{l.status || "available"}</span></td></tr>)}</tbody>
                        </table></div>
                      );
                    })()}
                  </div>
                  <div className="id-invpop-f"><button className="btn ghost" onClick={() => setInvModal(false)}>Close</button><button className="btn primary" onClick={() => { setInvModal(false); window.__setPage && window.__setPage("inv-items"); }}><Icon name="external-link" size={14} /> Open full inventory</button></div>
                </div>
              </div>, document.body)}
          </div>
        );
      })()}

      <div className="ing-detail-grid" style={{ display: "grid", gridTemplateColumns: "1.6fr 1fr", gap: 22 }}>
        {/* LEFT */}
        <div className="col" style={{ gap: 22 }}>
          {hasChangeRequest &&
          <div className="card pad" style={{ borderColor: resolved ? "var(--green-300)" : "#DDD6FE", background: resolved ? "var(--green-50)" : "#F4F3FF" }}>
              <div style={{ display: "flex", alignItems: "flex-start", gap: 12 }}>
                <div style={{ width: 38, height: 38, borderRadius: 10, background: resolved ? "var(--green-100)" : "#EDE9FE", color: resolved ? "var(--green-700)" : "#6938EF", display: "grid", placeItems: "center", flexShrink: 0 }}>
                  <Icon name={resolved ? "check-check" : "message-square-warning"} size={19} />
                </div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontWeight: 800, fontSize: 15, color: "var(--text-primary)" }}>{resolved ? "Change request resolved" : "Change request needs your attention"}</div>
                  <div className="muted" style={{ fontSize: 13, marginTop: 2 }}>
                    {resolved ? "You can resubmit this ingredient for review." : i.status === "compliance-review" ? "Flagged for compliance review, update the values, then resubmit." : "A reviewer requested changes to this ingredient's nutrition values."}
                  </div>
                </div>
                <div style={{ display: "flex", gap: 8, flexShrink: 0, flexWrap: "wrap" }}>
                  {!resolved && <button className="btn secondary sm" onClick={() => {window.__editIngredientId = i.id;setPage("edit-ingredient");}}><Icon name="pencil" size={14} /> Edit values</button>}
                  {!resolved ?
                <button className="btn primary sm" onClick={() => {setResolved(true);toast("Marked as resolved");}}><Icon name="check" size={14} /> Mark resolved</button> :
                <button className="btn primary sm" onClick={() => {toast("Resubmitted for review");setPage("ingredients");setActiveIngredient(null);}}><Icon name="upload-cloud" size={14} /> Resubmit</button>}
                </div>
              </div>
            </div>
          }

          <div className="card" style={{ padding: 0, overflow: "hidden" }}>
            <div style={{ aspectRatio: "16 / 7", background: (i.image || i.cover) ? `url("${i.image || i.cover}") ${(i.imagePosition && Number.isFinite(Number(i.imagePosition.x)) && Number.isFinite(Number(i.imagePosition.y))) ? `${i.imagePosition.x}% ${i.imagePosition.y}%` : "center"}/cover` : "linear-gradient(135deg, #DCEEC1, #B6DC85 60%, #9ACB5E)", display: "grid", placeItems: "center", position: "relative" }}>
              {!(i.image || i.cover) && <Icon name="leaf" size={72} stroke={1.4} style={{ color: "var(--green-700)", opacity: .9 }} />}
              <span className="tag" style={{ position: "absolute", left: 16, bottom: 16, background: "rgba(255,255,255,.9)", color: "var(--green-700)", border: "1px solid var(--green-200)", fontWeight: 700 }}>{i.category}</span>
            </div>
          </div>

          <div>
            <div className="tabs" style={{ marginBottom: 16 }}>
              {["overview", "macros", "fatty-acids", "micros", "allergens", "nutrition", "sourcing", "usage", "compliance", "versions"].map((t) =>
              <button key={t} className={`${tab === t ? "on" : ""} ${idFlag && ID_SEC_TAB[idFlag.sec] === t ? "tab-flagged" : ""}`} onClick={() => setTab(t)} style={{ textTransform: "capitalize" }}>{t === "macros" ? "Macronutrients" : t === "fatty-acids" ? "Fatty Acids" : t === "micros" ? "Micronutrients" : t}</button>
              )}
            </div>
            {idFlag &&
            <div className="detail-flag">
                <Icon name="flag" size={16} />
                <div style={{ flex: 1 }}>
                  <strong>Needs review · {idFlag.label}</strong>
                  <div className="detail-flag-sub">{idFlag.sec} › {idFlag.field}{idFlag.cur ? `, current: ${idFlag.cur}` : ""}</div>
                </div>
                <button className="btn ghost sm" onClick={() => setIdFlag(null)}><Icon name="x" size={14} /> Dismiss</button>
              </div>
            }

            {tab === "overview" &&
            <div className="card pad">
                <KeyValue grid items={[
              { k: "Canonical name", v: i.canonical },
              { k: "Category", v: i.category },
              { k: "Status", v: <StatusPill status={i.status} /> },
              { k: "Priority", v: <PriorityPill priority={i.priority} /> },
              { k: "Reviewer", v: i.reviewer ? i.reviewer.name : "Unassigned" },
              { k: "Last updated", v: formatDate(i.updated) }]
              } />
                <hr className="divider" />
                <div>
                  <div style={{ fontSize: 13, fontWeight: 700, color: "var(--gray-600)", marginBottom: 8, textTransform: "uppercase", letterSpacing: ".06em" }}>Quick facts</div>
                  <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
                    <span className="pill brand">{i.nutr.calories} kcal / 100g</span>
                    <span className="pill neutral">{i.nutr.p}g protein</span>
                    <span className="pill neutral">{i.nutr.c}g carbs</span>
                    <span className="pill neutral">{i.nutr.f}g fat</span>
                    <span className="pill neutral">{usedIn.length} recipes</span>
                  </div>
                </div>
              </div>
            }

            {tab === "nutrition" &&
            <div className="card pad ing-nutcard">
                {(() => {
                  const n = i.nutr || {};
                  const val = (keys) => { for (const k of keys) { if (n[k] != null && n[k] !== "") return n[k]; } return null; };
                  const fmt = (v, u) => v == null ? <span className="ingn-na">—</span> : <span>{v}<em>{u}</em></span>;
                  const macroRows = [
                    { key: "Protein", value: val(["p", "protein"]), total: 50, color: "#1E7A49", ic: "beef" },
                    { key: "Carbs", value: val(["c", "carbs"]), total: 100, color: "#E0902A", ic: "wheat" },
                    { key: "Fat", value: val(["f", "fat"]), total: 70, color: "#6938EF", ic: "droplet" },
                  ];
                  const SECTIONS = [
                    { title: "Macronutrients", items: [
                      ["Calories", val(["calories", "kcal"]), "kcal", "#F04438", "flame"],
                      ["Protein", val(["p", "protein"]), "g", "#1E7A49", "beef"],
                      ["Carbohydrates", val(["c", "carbs"]), "g", "#E0902A", "wheat"],
                      ["Total fat", val(["f", "fat"]), "g", "#6938EF", "droplet"],
                      ["Fibre", val(["fiber", "fibre"]), "g", "#0E9384", "sprout"],
                      ["Sugar", val(["sugar", "sugars"]), "g", "#EC4899", "candy"],
                    ]},
                    { title: "Fatty acids", items: [
                      ["Saturated", val(["satFat", "sat", "saturated"]), "g", "#A855F7", "droplets"],
                      ["Monounsaturated", val(["monoFat", "mono"]), "g", "#8B5CF6", "droplets"],
                      ["Polyunsaturated", val(["polyFat", "poly"]), "g", "#7C3AED", "droplets"],
                      ["Trans fat", val(["transFat", "trans"]), "g", "#DB2777", "droplets"],
                      ["Cholesterol", val(["cholesterol"]), "mg", "#9333EA", "activity"],
                      ["Omega-3", val(["omega3", "omega_3"]), "g", "#6366F1", "fish"],
                    ]},
                    { title: "Micronutrients", items: [
                      ["Sodium", val(["sodium"]), "mg", "#0EA5B7", "waves"],
                      ["Potassium", val(["potassium"]), "mg", "#0891B2", "zap"],
                      ["Calcium", val(["calcium"]), "mg", "#64748B", "bone"],
                      ["Iron", val(["iron"]), "mg", "#B45309", "magnet"],
                      ["Magnesium", val(["magnesium"]), "mg", "#0D9488", "gem"],
                      ["Zinc", val(["zinc"]), "mg", "#475569", "gem"],
                      ["Vitamin C", val(["vitC", "vitaminC"]), "mg", "#F59E0B", "citrus"],
                      ["Vitamin A", val(["vitA", "vitaminA"]), "µg", "#EA580C", "carrot"],
                      ["Vitamin D", val(["vitD", "vitaminD"]), "µg", "#EAB308", "sun"],
                    ]},
                  ];
                  return (
                    <React.Fragment>
                      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 16 }}>
                        <h3 style={{ fontFamily: "var(--serif)", fontSize: 20, margin: 0 }}>Nutrition · per 100g</h3>
                        <span className={`pill ${["approved", "published"].includes(i.status) ? "success" : "warning"}`}><Icon name={["approved", "published"].includes(i.status) ? "shield-check" : "clock"} size={12} stroke={2.4} /> {["approved", "published"].includes(i.status) ? "Nutrition reviewed" : "Pending nutrition review"}</span>
                      </div>
                      <div className="ingn-hero">
                        {CalorieRing ? <CalorieRing kcal={n.calories || 0} unit="kcal / 100g" max={900} /> : <div style={{ fontSize: 32, fontWeight: 800 }}>{n.calories || "—"}</div>}
                        <div className="ingn-bars">
                          {macroRows.map((m) => (
                            <div key={m.key} className="ingn-bar">
                              <div className="ingn-bar-top">
                                <span className="ingn-bar-k"><span className="ingn-bar-ic" style={{ background: m.color + "1A", color: m.color }}><Icon name={m.ic} size={12} stroke={2.4} /></span>{m.key}</span>
                                <span className="ingn-bar-v">{m.value == null ? "—" : m.value + "g"} <em>/ {m.total}g rec.</em></span>
                              </div>
                              <div className="progress" style={{ background: "var(--gray-100)" }}>
                                <i style={{ width: `${m.value == null ? 0 : Math.min(100, m.value / m.total * 100)}%`, background: m.color }} />
                              </div>
                            </div>
                          ))}
                        </div>
                      </div>
                      {SECTIONS.map((sec) => (
                        <div key={sec.title} className="ingn-sec">
                          <div className="ingn-sec-h">{sec.title}</div>
                          <div className="ingn-grid">
                            {sec.items.map(([k, v, u, c, ic]) => (
                              <button key={k} type="button" disabled={v == null} className={"ingn-chip" + (v == null ? " na" : " clickable")} style={v != null ? { background: c + "10", borderColor: c + "2E" } : null}
                                onClick={() => { if (v != null) setNutrInfo({ k, v, u, c, ic }); }}>
                                <span className="ingn-chip-ic" style={{ background: (v != null ? c : "#98A2B3") + "1F", color: v != null ? c : "#98A2B3" }}><Icon name={ic} size={13} stroke={2.2} /></span>
                                <span className="ingn-chip-tx"><b style={v != null ? { color: c } : null}>{fmt(v, u)}</b><span>{k}</span></span>
                              </button>
                            ))}
                          </div>
                        </div>
                      ))}
                      <div className="alert info" style={{ marginTop: 6 }}>
                        <Icon name="info" size={18} />
                        <div><strong>Curated & dietitian-reviewed.</strong><div style={{ marginTop: 2 }}>Values are per 100g edible portion. Confirm against your organization's standards before recommending.</div></div>
                      </div>
                    </React.Fragment>
                  );
                })()}
              </div>
            }

            {tab === "sourcing" &&
            <div className="card pad">
                <h3 style={{ fontFamily: "var(--serif)", fontSize: 20, margin: "0 0 12px" }}>Sourcing & data</h3>
                <KeyValue grid items={[
              { k: "Primary data source", v: i.sourceType || "Not supplied" },
              { k: "Source reference", v: i.sourceId || "Not supplied" },
              { k: "Source URL", v: i.sourceUrl ? <a href={i.sourceUrl} target="_blank" rel="noreferrer">Open source</a> : "Not supplied" },
              { k: "Reference serving", v: `${i.refServing || 100} ${i.defaultUnit || i.unit || "g"}` },
              { k: "Conversion factor", v: i.conversionFactor || "Not supplied" },
              { k: "Storage", v: i.storage || "Not supplied" },
              { k: "Shelf life", v: i.shelfLife == null ? "Not supplied" : `${i.shelfLife} days` }]
              } />
              </div>
            }

            {tab === "usage" &&
            <div className="card pad">
                <h3 style={{ fontFamily: "var(--serif)", fontSize: 20, margin: "0 0 12px" }}>Used in {usedIn.length} recipes</h3>
                <div className="col" style={{ gap: 8 }}>
                  {usedIn.map((u) =>
                <div key={u.id} style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "10px 12px", border: "1px solid var(--gray-200)", borderRadius: 10 }}>
                      <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
                        <div style={{ width: 32, height: 32, borderRadius: 8, background: "var(--green-50)", color: "var(--green-700)", display: "grid", placeItems: "center" }}><Icon name="utensils-crossed" size={15} /></div>
                        <div><div style={{ fontWeight: 700, fontSize: 13.5 }}>{u.n}</div><div className="muted" style={{ fontSize: 11 }}>{u.id}</div></div>
                      </div>
                      <Icon name="chevron-right" size={16} style={{ color: "var(--gray-400)" }} />
                    </div>
                )}
                </div>
              </div>
            }

            {tab === "compliance" &&
            <div className="card pad">
                <h3 style={{ fontFamily: "var(--serif)", fontSize: 20, margin: "0 0 12px" }}>Compliance checklist</h3>
                <div className="col" style={{ gap: 10 }}>
                  {compliance.map((c, idx) =>
                <div key={idx} style={{ display: "flex", alignItems: "center", gap: 10, fontSize: 14 }}>
                      <Icon name={c.ok ? "check-circle-2" : "alert-triangle"} size={18} style={{ color: c.ok ? "var(--success-600)" : "var(--warning-600)" }} />
                      <span style={{ color: "var(--text-primary)" }}>{c.t}</span>
                    </div>
                )}
                </div>
              </div>
            }

            {tab === "versions" && (() => {
              const cur = i.nutr || {};
              const prev = { calories: (cur.calories ?? 124) + 18, protein: (cur.protein ?? 12) - 1, carbs: (cur.carbs ?? 10) + 2, fat: (cur.fat ?? 8) + 1 };
              const rows = [["Calories", prev.calories, cur.calories ?? 124, "kcal"], ["Protein", prev.protein, cur.protein ?? 12, "g"], ["Carbs", prev.carbs, cur.carbs ?? 10, "g"], ["Fat", prev.fat, cur.fat ?? 8, "g"]];
              return (
                <div className="card pad">
                  <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 4 }}>
                    <h3 style={{ fontFamily: "var(--serif)", fontSize: 20, margin: 0 }}>Version compare</h3>
                    <span className="pill neutral">v3 · current</span>
                  </div>
                  <p className="muted" style={{ fontSize: 13, margin: "0 0 16px" }}>What changed between the previous approved version and the current one.</p>
                  <div className="ivc-head"><span>Nutrient</span><span>v2 (prev)</span><span></span><span>v3 (current)</span><span>Change</span></div>
                  {rows.map(([k, a, b, u]) => {
                    const d = Math.round((b - a) * 10) / 10;
                    return (
                      <div key={k} className="ivc-row">
                        <span className="ivc-k">{k}</span>
                        <span className="ivc-old">{a}{u}</span>
                        <span className="ivc-arr"><Icon name="arrow-right" size={13} /></span>
                        <span className="ivc-new">{b}{u}</span>
                        <span className={"ivc-delta " + (d > 0 ? "up" : d < 0 ? "down" : "same")}>{d > 0 ? "+" : ""}{d}{u}</span>
                      </div>
                    );
                  })}
                  <div className="ivc-log">
                    <div className="ivc-log-h">Version history</div>
                    {[["v3", "Current", "Nutrition re-verified against master record", "Eve Nakamura", "2 days ago"], ["v2", "Superseded", "Supplier spec updated; sodium corrected", "Dana Liu", "3 weeks ago"], ["v1", "Original", "Ingredient created", "Sarah Chen", "2 months ago"]].map(([v, tag, note, who, when]) => (
                      <div key={v} className="ivc-log-row">
                        <span className={"ivc-log-ver" + (v === "v3" ? " on" : "")}>{v}</span>
                        <div className="ivc-log-tx"><b>{note}</b><span>{who} · {when}</span></div>
                        <span className="pill neutral" style={{ fontSize: 10.5 }}>{tag}</span>
                      </div>
                    ))}
                  </div>
                </div>
              );
            })()}

            {tab === "macros" &&
            <div className="card pad">
                <h3 style={{ fontFamily: "var(--serif)", fontSize: 20, margin: "0 0 14px" }}>Macronutrients <span className="muted" style={{ fontSize: 13 }}>· per 100{i.nutr?.baseUnit || "g"}</span></h3>
                <div style={{ display: "grid", gridTemplateColumns: "repeat(3,1fr)", gap: 10 }}>
                  {[["Calories", (i.nutr?.calories ?? 124) + " kcal"], ["Protein", (i.nutr?.protein ?? 12) + " g"], ["Carbs", (i.nutr?.carbs ?? 10) + " g"], ["Fat", (i.nutr?.fat ?? 8) + " g"], ["Sugar", "3 g"], ["Added sugar", "0 g"], ["Fiber", "4 g"], ["Caffeine", "0 mg"], ["Sodium", "74 mg"]].map(([k, v]) =>
                <div key={k} style={{ background: "var(--gray-50)", border: "1px solid var(--gray-100)", borderRadius: 11, padding: "12px 10px", textAlign: "center" }}>
                      <div style={{ fontSize: 19, fontWeight: 800 }}>{v}</div><div className="muted" style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: ".04em", fontWeight: 700, marginTop: 3 }}>{k}</div>
                    </div>
                )}
                </div>
              </div>
            }

            {tab === "fatty-acids" &&
            <div className="card pad">
                <h3 style={{ fontFamily: "var(--serif)", fontSize: 20, margin: "0 0 14px" }}>Fatty acids &amp; cholesterol</h3>
                <div className="col" style={{ gap: 0 }}>
                  {[["Saturated fat", "2.8 g"], ["Monounsaturated fat", "3.1 g"], ["Polyunsaturated fat", "1.4 g"], ["Trans fat", "0 g"], ["Cholesterol", "0 mg"]].map(([k, v], idx) =>
                <div key={k} style={{ display: "flex", justifyContent: "space-between", padding: "11px 4px", borderBottom: idx < 4 ? "1px solid var(--gray-100)" : "none", fontSize: 14 }}><span>{k}</span><strong className="cc-num">{v}</strong></div>
                )}
                </div>
              </div>
            }

            {tab === "micros" &&
            <div className="card pad">
                <h3 style={{ fontFamily: "var(--serif)", fontSize: 20, margin: "0 0 14px" }}>Micronutrients</h3>
                <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: ".05em", textTransform: "uppercase", color: "var(--gray-500)", margin: "0 0 8px" }}>Minerals</div>
                <div style={{ display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 8 }}>
                  {[["Calcium", "120 mg"], ["Iron", "1.8 mg"], ["Magnesium", "34 mg"], ["Phosphorus", "210 mg"], ["Potassium", "210 mg"], ["Sodium", "74 mg"], ["Zinc", "0.9 mg"], ["Copper", "0.1 mg"], ["Manganese", "0.2 mg"], ["Selenium", "22 mcg"]].map(([k, v]) =>
                <div key={k} style={{ background: "var(--gray-50)", border: "1px solid var(--gray-100)", borderRadius: 9, padding: "9px 8px" }}>
                      <div className="muted" style={{ fontSize: 10.5, fontWeight: 700 }}>{k}</div><div style={{ fontSize: 14, fontWeight: 800, marginTop: 2 }}>{v}</div>
                    </div>
                )}
                </div>
                <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: ".05em", textTransform: "uppercase", color: "var(--gray-500)", margin: "16px 0 8px" }}>Vitamins</div>
                <div style={{ display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 8 }}>
                  {[["Vitamin C", "12 mg"], ["Vitamin B1 (Thiamine)", "0.1 mg"], ["Vitamin B2 (Riboflavin)", "0.1 mg"], ["Vitamin B3 (Niacin)", "1.2 mg"], ["Vitamin B5 (Pantothenic)", "0.6 mg"], ["Vitamin B6 (Pyridoxine)", "0.3 mg"], ["Folate", "28 mcg"], ["Vitamin B12", "0.4 mcg"], ["Vitamin A", "40 mcg"], ["Vitamin E", "1.1 mg"], ["Vitamin K", "8 mcg"], ["Vitamin D", "0 mcg"]].map(([k, v]) =>
                <div key={k} style={{ background: "var(--gray-50)", border: "1px solid var(--gray-100)", borderRadius: 9, padding: "9px 8px" }}>
                      <div className="muted" style={{ fontSize: 10.5, fontWeight: 700 }}>{k}</div><div style={{ fontSize: 14, fontWeight: 800, marginTop: 2 }}>{v}</div>
                    </div>
                )}
                </div>
              </div>
            }

            {tab === "allergens" &&
            <div className="card pad">
                <h3 style={{ fontFamily: "var(--serif)", fontSize: 20, margin: "0 0 14px" }}>Allergens</h3>
                <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
                  {(i.allergens && i.allergens.length ? i.allergens : []).length ?
                i.allergens.map((a) => <span key={a} className="pill error" style={{ justifyContent: "center" }}><Icon name="alert-triangle" size={12} /> {a}</span>) :
                <span className="pill success" style={{ justifyContent: "center" }}><Icon name="check" size={12} /> None declared</span>}
                </div>
              </div>
            }
          </div>
        </div>

        {/* RIGHT */}
        <div className="col" style={{ gap: 18 }}>
          <div className="card pad">
            <h3 style={{ fontFamily: "var(--serif)", fontSize: 18, margin: "0 0 12px" }}>Contributor</h3>
            <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
              <PersonAvatar person={i.contributor} className="avatar lg" style={{ background: "var(--gray-100)", color: "var(--gray-700)" }} />
              <div>
                <div style={{ fontWeight: 700 }}>{i.contributor.name}</div>
                <div className="muted" style={{ fontSize: 13 }}>Media Contributor · 12 published</div>
              </div>
            </div>
            <hr className="divider" />
            {KeyValue && <KeyValue items={[
            { k: "Comments", v: `${i.comments || 0}` },
            { k: "Completion", v: CompletionBar ? <CompletionBar pct={i.status === "approved" ? 100 : i.status === "draft" ? 30 : 75} /> : "—" }]
            } />}
          </div>

          <div className="card pad">
            <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 8 }}>
              <h3 style={{ fontFamily: "var(--serif)", fontSize: 18, margin: 0 }}>Review timeline</h3>
              <span className="pill neutral">{i.comments || 0} comments</span>
            </div>
            {flow ? <WorkflowTimeline flow={flow} status={i.status} /> : (Timeline && <Timeline status={i.status} />)}
            <hr className="divider" />
            <textarea className="textarea" rows="3" placeholder="Leave a comment…" value={comment} onChange={(e) => setComment(e.target.value)} />
            <div style={{ display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 8 }}>
              <button className="btn sm primary" disabled={!comment.trim()} onClick={() => {toast("Comment posted");setComment("");}}><Icon name="send" size={14} /> Post</button>
            </div>
          </div>
        </div>
      </div>

      {flow && flow.primary && <ReviewCompleteModal open={modal === "complete"} flow={flow} onClose={() => setModal(null)} onConfirm={confirmComplete} onRequestChanges={requestChanges} />}

      <Modal open={modal === "delete"} onClose={() => setModal(null)} title="Delete published ingredient" subtitle="This removes the live ingredient from the master library." footer={
        <>
          <button className="btn ghost" onClick={() => setModal(null)}>Cancel</button>
          <button className="btn danger" onClick={() => { setModal(null); window.wePatchItem("ingredient", i.id, { status: "draft" }); toast(`Deleted “${i.name}”`); setActiveIngredient(null); setPage("ingredients"); }}><Icon name="trash-2" size={14} /> Delete ingredient</button>
        </>
      }>
        <div className="alert error" style={{ marginBottom: 14 }}>
          <Icon name="alert-triangle" size={18} />
          <div><strong>Admin action</strong><div style={{ marginTop: 2 }}>Published content is normally locked. Deleting it will unpublish it and unlink it from any recipes that reference it.</div></div>
        </div>
      </Modal>

      <Modal open={modal === "approve"} onClose={() => setModal(null)} title="Approve ingredient" subtitle="Mark this master ingredient as verified." footer={
      <>
          <button className="btn ghost" onClick={() => setModal(null)}>Cancel</button>
          <button className="btn primary" onClick={() => {setModal(null);toast("Ingredient approved");setPage("ingredients");setActiveIngredient(null);}}><Icon name="check" size={14} /> Approve</button>
        </>
      }>
        <div className="alert success" style={{ marginBottom: 14 }}>
          <Icon name="shield-check" size={18} />
          <div><strong>Data verified</strong><div style={{ marginTop: 2 }}>Nutrition values match USDA FoodData Central.</div></div>
        </div>
      </Modal>

      <Modal open={modal === "changes-unused"} onClose={() => setModal(null)} title="Request Changes" subtitle="Send feedback back to the contributor." footer={
      <>
          <button className="btn ghost" onClick={() => setModal(null)}>Cancel</button>
          <button className="btn primary" onClick={() => {setModal(null);toast("Changes requested");}}><Icon name="send" size={14} /> Send feedback</button>
        </>
      }>
        <div className="field"><label>What needs to change?</label>
          <textarea className="textarea" rows="4" defaultValue="Please attach the source documentation and double-check the fat value per 100g." />
        </div>
      </Modal>

      <Modal open={modal === "reject"} onClose={() => setModal(null)} title="Reject ingredient" subtitle="The contributor will be notified with your reason." footer={
      <>
          <button className="btn ghost" onClick={() => setModal(null)}>Cancel</button>
          <button className="btn danger" onClick={() => {setModal(null);toast("Ingredient rejected");}}><Icon name="x" size={14} /> Reject</button>
        </>
      }>
        <div className="alert error" style={{ marginBottom: 14 }}>
          <Icon name="alert-triangle" size={18} />
          <div><strong>This action can be reversed</strong><div style={{ marginTop: 2 }}>Rejected ingredients move back to the contributor's drafts.</div></div>
        </div>
        <div className="field"><label>Reason</label><textarea className="textarea" rows="3" placeholder="Explain why you're rejecting…" /></div>
      </Modal>

      <Modal open={modal === "urgent"} onClose={() => setModal(null)} title="Mark for urgent review" subtitle="Escalate this ingredient so the right reviewer looks at it today." footer={
      <>
          <button className="btn ghost" onClick={() => setModal(null)}>Cancel</button>
          <button className="btn primary" disabled={!urgentTo} onClick={() => {
            const p = urgentPeople.find((x) => x.initials === urgentTo);
            urgentAdd({ key: "u-" + Date.now(), kind: "ingredient", rid: i.id, name: i.name, sub: `${i.canonical} · ${i.category}`,
              toInitials: p ? p.initials : urgentTo, toName: p ? p.name : urgentTo, toRole: p ? p.role : null,
              byName: me.name, byInitials: me.initials, reason: urgentNote.trim() || "Marked for urgent review", when: new Date().toISOString().slice(0, 10), severity: "high" });
            setModal(null); toast(`Escalated to ${p ? p.name : "reviewer"}, added to Urgent review`);
          }}><Icon name="alert-triangle" size={14} /> Escalate</button>
        </>
      }>
        <div className="alert warning" style={{ marginBottom: 14 }}>
          <Icon name="alert-triangle" size={18} />
          <div><strong>Sets priority to high</strong><div style={{ marginTop: 2 }}>It moves to the top of the chosen reviewer's queue and appears under Urgent review.</div></div>
        </div>
        <div className="field">
          <label>Escalate to <span className="muted" style={{ fontWeight: 500 }}>· available to {ROLES[role] ? ROLES[role].label : role}</span></label>
          <div className="col" style={{ gap: 6, marginTop: 4 }}>
            {urgentPeople.map((p) => (
              <button key={p.id} type="button" className="kb-assignee-opt" onClick={() => setUrgentTo(p.initials)} style={{ border: `1.5px solid ${urgentTo === p.initials ? "var(--green-400)" : "var(--gray-200)"}`, background: urgentTo === p.initials ? "var(--green-50)" : "#fff", borderRadius: 10 }}>
                <PersonAvatar person={p} tag="span" className="avatar sm" style={{ background: "var(--gray-100)", color: "var(--gray-700)" }} />
                <span style={{ flex: 1, textAlign: "left", minWidth: 0 }}>
                  <span style={{ display: "block", fontSize: 14, fontWeight: 700 }}>{p.name}</span>
                  <span className="muted" style={{ fontSize: 12 }}>{ROLES[p.role] ? ROLES[p.role].label : p.role} · {p.team}</span>
                </span>
                {urgentTo === p.initials && <Icon name="check" size={16} style={{ color: "var(--green-700)", flexShrink: 0 }} />}
              </button>
            ))}
            {urgentPeople.length === 0 && <span className="muted" style={{ fontSize: 13 }}>No reviewers available to escalate to from your role.</span>}
          </div>
        </div>
        <div className="field" style={{ marginTop: 12 }}><label>Why is this urgent? <span className="muted" style={{ fontWeight: 500 }}>· optional</span></label>
          <textarea className="textarea" rows="3" value={urgentNote} onChange={(e) => setUrgentNote(e.target.value)} placeholder="e.g. allergen risk, SLA breach, publishing today…" />
        </div>
      </Modal>

      {nutrInfo && (() => {
        const edu = NUTRIENT_EDU[nutrInfo.k] || { what: "A tracked nutrient in this ingredient.", why: "Contributes to the ingredient's overall nutrition profile.", who: "Considered across the organization's dietary programs." };
        return ReactDOM.createPortal((
          <div className="nutedu-scrim" onMouseDown={(e) => { if (e.target === e.currentTarget) setNutrInfo(null); }}>
            <div className="nutedu-drawer" style={{ "--c": nutrInfo.c }}>
              <div className="nutedu-head" style={{ background: nutrInfo.c + "12" }}>
                <span className="nutedu-ic" style={{ background: nutrInfo.c + "22", color: nutrInfo.c }}><Icon name={nutrInfo.ic} size={20} stroke={2.2} /></span>
                <div className="nutedu-head-tx">
                  <span className="nutedu-k">{nutrInfo.k}</span>
                  <span className="nutedu-v" style={{ color: nutrInfo.c }}>{nutrInfo.v}{nutrInfo.u} <em>per 100g</em></span>
                </div>
                <button className="nutedu-x" onClick={() => setNutrInfo(null)}><Icon name="x" size={18} /></button>
              </div>
              <div className="nutedu-body">
                <div className="nutedu-sec"><div className="nutedu-sec-h" style={{ color: nutrInfo.c }}><Icon name="info" size={14} stroke={2.2} /> What it is</div><p>{edu.what}</p></div>
                <div className="nutedu-sec"><div className="nutedu-sec-h" style={{ color: nutrInfo.c }}><Icon name="heart-pulse" size={14} stroke={2.2} /> Why it matters</div><p>{edu.why}</p></div>
                <div className="nutedu-sec"><div className="nutedu-sec-h" style={{ color: nutrInfo.c }}><Icon name="users" size={14} stroke={2.2} /> Who it affects</div><p>{edu.who}</p></div>
                {edu.dv && <div className="nutedu-facts">
                  <div className="nutedu-fact"><span><Icon name="target" size={13} /> Daily value</span><b>{edu.dv}</b></div>
                  {edu.sources && <div className="nutedu-fact"><span><Icon name="apple" size={13} /> Food sources</span><b>{edu.sources}</b></div>}
                </div>}
                {edu.low && <div className="nutedu-sec"><div className="nutedu-sec-h" style={{ color: "#B54708" }}><Icon name="trending-down" size={14} stroke={2.2} /> Too little</div><p>{edu.low}</p></div>}
                {edu.high && <div className="nutedu-sec"><div className="nutedu-sec-h" style={{ color: "#B54708" }}><Icon name="trending-up" size={14} stroke={2.2} /> Too much</div><p>{edu.high}</p></div>}
                {edu.policy && <div className="nutedu-policy"><Icon name="shield-check" size={14} stroke={2.2} /><span><b>Compliance relevance.</b> {edu.policy}</span></div>}
                <div className="nutedu-inhere" style={{ borderColor: nutrInfo.c + "33", background: nutrInfo.c + "0A" }}>
                  <Icon name="flask-conical" size={15} stroke={2.2} style={{ color: nutrInfo.c }} />
                  <span><b>{i.name}</b> provides <b style={{ color: nutrInfo.c }}>{nutrInfo.v}{nutrInfo.u}</b> of {nutrInfo.k.toLowerCase()} per 100g edible portion — curated &amp; dietitian-reviewed.</span>
                </div>
              </div>
            </div>
          </div>
        ), document.body);
      })()}
    </div>);

}

/* Feedback, unified queue combining recipes + ingredients */
function FeedbackPage() {
  const { openRecipe, openIngredient, openReview, role, toast } = useApp();
  const [type, setType] = React.useState("all");
  const [statusF, setStatusF] = React.useState("all");

  const FB = typeof FEEDBACK !== "undefined" ? FEEDBACK : [];
  const RX = typeof RECIPES !== "undefined" ? RECIPES : [];
  const reviewStatuses = ["pending-review", "compliance-review", "changes-requested", "rejected", "awaiting-attention"];

  const items = React.useMemo(() => {
    const rec = RX.map((r) => {
      const fb = FB.find((f) => f.recipe === r.id)?.items || [];
      const last = fb[0];
      return { kind: "recipe", id: r.id, name: r.name, sub: `${r.cuisine} · ${r.category}`, status: r.status, priority: r.priority,
        comments: fb.length, last: last ? { who: last.from, when: last.when, text: last.text } : null,
        contributor: r.contributor, updated: r.submitted, raw: r };
    }).filter((x) => reviewStatuses.includes(x.status) || x.comments > 0);

    const ing = INGREDIENT_ITEMS.map((i) => ({
      kind: "ingredient", id: i.id, name: i.name, sub: `${i.canonical} · ${i.category}`, status: i.status, priority: i.priority,
      comments: i.comments, last: i.comments > 0 && i.reviewer ? { who: i.reviewer.name, when: formatDate(i.updated), text: i.status === "changes-requested" ? "Requested changes to nutrition values." : i.status === "compliance-review" ? "Flagged for compliance review." : "Left a review note." } : null,
      contributor: i.contributor, reviewer: i.reviewer, updated: i.updated, raw: i
    })).filter((x) => reviewStatuses.includes(x.status) || x.comments > 0);

    let all = [...rec, ...ing];
    if (type === "recipes") all = all.filter((x) => x.kind === "recipe");
    if (type === "ingredients") all = all.filter((x) => x.kind === "ingredient");
    if (statusF !== "all") all = all.filter((x) => x.status === statusF);
    const prank = { high: 0, medium: 1, low: 2 };
    all.sort((a, b) => prank[a.priority] - prank[b.priority] || b.comments - a.comments || b.updated.localeCompare(a.updated));
    return all;
  }, [type, statusF]);

  const recCount = React.useMemo(() => RX.filter((r) => reviewStatuses.includes(r.status) || (FB.find((f) => f.recipe === r.id)?.items.length || 0) > 0).length, []);
  const ingCount = React.useMemo(() => INGREDIENT_ITEMS.filter((i) => reviewStatuses.includes(i.status) || i.comments > 0).length, []);
  const totalComments = items.reduce((n, x) => n + x.comments, 0);

  const open = (x) => openReview(x.raw, x.kind);
  const statusTabs = [["all", "All"], ["pending-review", "Pending"], ["compliance-review", "Compliance"], ["changes-requested", "Changes requested"]];

  return (
    <div className="ing-lib">
      <Crumbs path={[{ label: "Feedback" }]} />
      <div className="page-head">
        <div>
          <h1 className="page-title">Feedback</h1>
          <p className="page-sub">Items a reviewer sent back to you. Open one to fix each flagged issue and resubmit. <span className="muted">(Browse everything in <strong>Library</strong>.)</span></p>
        </div>
        <div style={{ display: "flex", gap: 10, flexWrap: "wrap" }}>
          <button className="btn secondary"><Icon name="check-check" size={16} /> Mark all read</button>
        </div>
      </div>

      {/* Type tabs */}
      <div className="tabs" style={{ marginBottom: 16 }}>
        {[["all", "All", recCount + ingCount], ["recipes", "Recipes", recCount], ["ingredients", "Ingredients", ingCount]].map(([id, label, count]) =>
        <button key={id} className={type === id ? "on" : ""} onClick={() => setType(id)}>
            {label}
            <span style={{ marginLeft: 6, fontSize: 11, padding: "1px 7px", borderRadius: 999, background: type === id ? "var(--green-100)" : "var(--gray-100)", color: type === id ? "var(--green-700)" : "var(--gray-600)", fontWeight: 700 }}>{count}</span>
          </button>
        )}
      </div>

      {/* Status filter */}
      <div style={{ display: "flex", gap: 8, marginBottom: 16, flexWrap: "wrap" }}>
        {statusTabs.map(([id, label]) =>
        <button key={id} className={`chip ${statusF === id ? "on" : ""}`} onClick={() => setStatusF(id)}>{label}</button>
        )}
      </div>

      <div className="card" style={{ overflow: "hidden" }}>
        <div className="ing-tablewrap">
          <table className="table" style={{ minWidth: 880 }}>
            <thead><tr>
              <th>Item</th><th>Type</th><th>Latest feedback</th><th>From</th><th>Status</th><th>Comments</th><th>Updated</th><th style={{ textAlign: "right" }}>Action</th>
            </tr></thead>
            <tbody>
              {items.map((x) =>
              <tr key={x.kind + x.id} style={{ cursor: "pointer" }} onClick={() => open(x)}>
                  <td>
                    <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
                      {x.kind === "recipe" ?
                    <div className="thumb sm" style={{ backgroundImage: `url("${x.raw.cover}")` }} /> :
                    <div className="thumb sm" style={{ background: "linear-gradient(135deg, #DCEEC1, #C9E2A4)", display: "grid", placeItems: "center", color: "var(--green-700)" }}><Icon name="leaf" size={18} stroke={1.8} /></div>}
                      <div>
                        <div style={{ fontWeight: 600, color: "var(--text-primary)" }}>{x.name}</div>
                        <div className="muted" style={{ fontSize: 12 }}>{x.sub}</div>
                      </div>
                    </div>
                  </td>
                  <td>
                    <span className="pill" style={x.kind === "recipe" ?
                  { background: "var(--green-50)", color: "var(--green-700)", border: "1px solid var(--green-200)" } :
                  { background: "#EEF4FF", color: "#3538CD", border: "1px solid #C7D7FE" }}>
                      <Icon name={x.kind === "recipe" ? "utensils-crossed" : "leaf"} size={11} stroke={2.4} /> {x.kind === "recipe" ? "Recipe" : "Ingredient"}
                    </span>
                  </td>
                  <td style={{ maxWidth: 280 }}>
                    {x.last ?
                  <div className="muted" style={{ fontSize: 12.5, lineHeight: 1.4 }}><span style={{ color: "var(--text-primary)", fontWeight: 600 }}>{x.last.who}:</span> “{x.last.text.length > 60 ? x.last.text.slice(0, 60) + "…" : x.last.text}”</div> :
                  <span className="muted" style={{ fontSize: 12.5, fontStyle: "italic" }}>Awaiting first review</span>}
                  </td>
                  <td>
                    <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                      <PersonAvatar person={x.contributor} className="avatar sm" style={{ background: "var(--gray-100)", color: "var(--gray-700)" }} />
                      <span style={{ fontSize: 13 }}>{x.contributor.name}</span>
                    </div>
                  </td>
                  <td><StatusPill status={x.status} /></td>
                  <td>
                    <span style={{ display: "inline-flex", alignItems: "center", gap: 4, fontSize: 13, color: "var(--gray-700)", fontWeight: 600 }}>
                      <Icon name="message-circle" size={13} stroke={2.4} /> {x.comments}
                    </span>
                  </td>
                  <td className="muted" style={{ fontSize: 13 }}>{formatDate(x.updated)}</td>
                  <td style={{ textAlign: "right" }} onClick={(e) => e.stopPropagation()}>
                    <button className="btn sm primary" onClick={() => open(x)}>Review <Icon name="arrow-right" size={14} /></button>
                  </td>
                </tr>
              )}
            </tbody>
          </table>
        </div>
        {items.length === 0 &&
        <div className="empty">
            <div className="icon"><Icon name="check-check" size={24} /></div>
            <h3>No feedback here</h3>
            <p>Nothing in this view needs your attention right now.</p>
          </div>
        }
      </div>
    </div>);

}

/* Edit Ingredient, list page with search; click row → loads form in iframe */
function EditIngredientPage() {
  const [query, setQuery] = aUseState("");
  const [editingId, setEditingId] = aUseState(() => {const id = window.__editIngredientId;window.__editIngredientId = null;return id || null;});
  const ingredients = [
  { id: "ing_171477", name: "Quinoa Cooked", canonical: "Chenopodium quinoa", category: "Grain", supplier: "Tyson Food", source: "USDA Food data center", updated: "24h 6m ago", img: "https://images.unsplash.com/photo-1505253716362-afaea1d3d1af?auto=format&fit=crop&w=120&h=120&q=70" },
  { id: "ing_169704", name: "Carrot", canonical: "Daucus carota", category: "Vegetable", supplier: "Tyson Food", source: "USDA Food data center", updated: "24h 6m ago", img: "https://images.unsplash.com/photo-1598170845058-32b9d6a5da37?auto=format&fit=crop&w=120&h=120&q=70" },
  { id: "ing_173441", name: "Tomato paste", canonical: "Solanum lycopersicum", category: "Vegetable", supplier: "Tyson Food", source: "USDA Food data center", updated: "24h 6m ago", img: "https://images.unsplash.com/photo-1592924357228-91a4daadcfea?auto=format&fit=crop&w=120&h=120&q=70" },
  { id: "ing_175167", name: "Scotch Bonnet", canonical: "Capsicum chinense", category: "Vegetable", supplier: "Tyson Food", source: "USDA Food data center", updated: "24h 6m ago", img: "https://images.unsplash.com/photo-1583119022894-919a68a3d0e3?auto=format&fit=crop&w=120&h=120&q=70" },
  { id: "ing_171287", name: "Skimmed Milk", canonical: "Bos taurus", category: "Dairy", supplier: "Tyson Food", source: "USDA Food data center", updated: "24h 6m ago", img: "https://images.unsplash.com/photo-1550583724-b2692b85b150?auto=format&fit=crop&w=120&h=120&q=70" },
  { id: "ing_168474", name: "Greek Yogurt, plain", canonical: "Lactobacillus cultures", category: "Dairy", supplier: "Tyson Food", source: "USDA Food data center", updated: "24h 6m ago", img: "https://images.unsplash.com/photo-1488477181946-6428a0291777?auto=format&fit=crop&w=120&h=120&q=70" },
  { id: "ing_168409", name: "Black bean, cooked", canonical: "Phaseolus vulgaris", category: "Grain", supplier: "Tyson Food", source: "USDA Food data center", updated: "24h 6m ago", img: "https://images.unsplash.com/photo-1604908176997-125f25cc6f3d?auto=format&fit=crop&w=120&h=120&q=70" }];

  const filtered = ingredients.filter((i) =>
  !query.trim() ||
  i.name.toLowerCase().includes(query.toLowerCase()) ||
  i.canonical.toLowerCase().includes(query.toLowerCase()) ||
  i.category.toLowerCase().includes(query.toLowerCase())
  );
  if (editingId) {
    return <IframeWithLoraaFade src={"screens/create-ingredient.html?v=20260828-public-catalog-media-1&edit=" + editingId} title="Edit Ingredient" />;
  }
  return (
    <div className="page">
      <div className="page-head">
        <div>
          <h1 className="page-title">Edit Ingredient</h1>
          <p className="page-sub">{filtered.length} of {ingredients.length} ingredients, click any row to edit nutrition, allergens, sources.</p>
        </div>
      </div>
      <div className="card" style={{ overflow: "hidden" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 12, padding: "14px 18px", borderBottom: "1px solid var(--gray-100)" }}>
          <div style={{ flex: 1, display: "flex", alignItems: "center", gap: 10, height: 40, padding: "0 13px", border: "1px solid var(--gray-200)", borderRadius: 10, background: "var(--gray-50)" }}>
            <Icon name="search" size={16} style={{ color: "var(--gray-500)" }} />
            <input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search name, category, supplier..."
            style={{ flex: 1, border: 0, outline: 0, background: "transparent", fontSize: 14, fontFamily: "inherit", color: "var(--text-primary)" }} />
          </div>
          <button className="btn secondary"><Icon name="sliders-horizontal" size={15} /> Filter</button>
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "minmax(220px,1.6fr) 120px 130px 180px 150px 80px", gap: 14, alignItems: "center", padding: "11px 18px", borderBottom: "1px solid var(--gray-200)", background: "var(--gray-50)" }}>
          {["Ingredient name", "Category", "Supplier", "Source type", "Last Updated", "Action"].map((h) =>
          <span key={h} style={{ fontSize: 10.5, fontWeight: 800, letterSpacing: ".05em", textTransform: "uppercase", color: "var(--gray-500)" }}>{h}</span>
          )}
        </div>
        {filtered.length === 0 ?
        <div className="empty"><div className="icon"><Icon name="search-x" size={24} /></div><h3>No matches</h3><p>Try a different search term.</p></div> :
        filtered.map((i, idx) =>
        <div key={i.id}
        className="ei-row"
        onClick={() => setEditingId(i.id)}
        style={{
          display: "grid", gridTemplateColumns: "minmax(220px,1.6fr) 120px 130px 180px 150px 80px", gap: 14, alignItems: "center",
          padding: "12px 18px", cursor: "pointer"
        }}>
            <div style={{ display: "flex", alignItems: "center", gap: 12, minWidth: 0 }}>
              <div style={{ width: 44, height: 44, borderRadius: 9, overflow: "hidden", flexShrink: 0, background: "var(--gray-100)" }}>
                {i.img ?
              <img src={i.img} alt={i.name} style={{ width: "100%", height: "100%", objectFit: "cover" }} /> :
              <div style={{ width: "100%", height: "100%", background: "linear-gradient(135deg, #DCEEC1, #C9E2A4)", display: "grid", placeItems: "center", color: "var(--green-700)" }}><Icon name="leaf" size={18} stroke={1.8} /></div>}
              </div>
              <div style={{ minWidth: 0 }}>
                <div style={{ fontWeight: 700, fontSize: 14, color: "var(--text-primary)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{i.name}</div>
                <div style={{ fontSize: 12, color: "var(--gray-500)", fontStyle: "italic", marginTop: 2 }}>{i.canonical}</div>
              </div>
            </div>
            <span className="tag" style={{ background: "var(--green-50)", color: "var(--green-700)", border: "1px solid var(--green-200)", fontSize: 11, justifySelf: "start" }}>{i.category}</span>
            <span style={{ fontSize: 13, color: "var(--gray-600)" }}>{i.supplier}</span>
            <span style={{ fontSize: 13, color: "var(--gray-600)" }}>{i.source}</span>
            <span style={{ fontSize: 12.5, color: "var(--gray-500)" }}>Updated {i.updated}</span>
            <span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 13, fontWeight: 700, color: "var(--green-700)" }}><Icon name="pencil" size={15} stroke={2} /> Edit</span>
          </div>
        )}
      </div>
    </div>);

}

/* Edit Recipe, searchable list; click a row → loads the recipe builder in an iframe */
function EditRecipePage() {
  const { openRecipe, role } = useApp();
  const canEditPub = window.canEditPublished ? window.canEditPublished(role) : (role === "admin" || role === "super-admin");
  const [query, setQuery] = aUseState("");
  const [editingId, setEditingId] = aUseState(() => { const id = window.__editRecipeId; window.__editRecipeId = null; return id || null; });
  const list = RECIPES;
  const filtered = list.filter((r) =>
    !query.trim() ||
    r.name.toLowerCase().includes(query.toLowerCase()) ||
    r.cuisine.toLowerCase().includes(query.toLowerCase()) ||
    r.contributor.name.toLowerCase().includes(query.toLowerCase())
  );
  if (editingId) {
    return <IframeWithLoraaFade src={"screens/add-recipe.html?v=20260828-public-catalog-media-1&edit=" + editingId} title="Edit Recipe" />;
  }
  return (
    <div className="page">
      <div className="page-head">
        <div>
          <h1 className="page-title">Edit Recipe</h1>
          <p className="page-sub">{filtered.length} of {list.length} recipes, click any row to edit its details, media, nutrition, and tags.</p>
        </div>
      </div>
      <div className="card" style={{ overflow: "hidden" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 12, padding: "14px 18px", borderBottom: "1px solid var(--gray-100)" }}>
          <div style={{ flex: 1, display: "flex", alignItems: "center", gap: 10, height: 40, padding: "0 13px", border: "1px solid var(--gray-200)", borderRadius: 10, background: "var(--gray-50)" }}>
            <Icon name="search" size={16} style={{ color: "var(--gray-500)" }} />
            <input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search recipe, cuisine, contributor..."
              style={{ flex: 1, border: 0, outline: 0, background: "transparent", fontSize: 14, fontFamily: "inherit", color: "var(--text-primary)" }} />
          </div>
          <button className="btn secondary"><Icon name="sliders-horizontal" size={15} /> Filter</button>
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "minmax(220px,1.6fr) 130px 160px 150px 80px", gap: 14, alignItems: "center", padding: "11px 18px", borderBottom: "1px solid var(--gray-200)", background: "var(--gray-50)" }}>
          {["Recipe", "Cuisine", "Contributor", "Status", "Action"].map((h) =>
            <span key={h} style={{ fontSize: 10.5, fontWeight: 800, letterSpacing: ".05em", textTransform: "uppercase", color: "var(--gray-500)" }}>{h}</span>
          )}
        </div>
        {filtered.length === 0 ?
          <div className="empty"><div className="icon"><Icon name="search-x" size={24} /></div><h3>No matches</h3><p>Try a different search term.</p></div> :
          filtered.map((r) => {
            const locked = r.status === "published" && !canEditPub;
            return (
            <div key={r.id}
              className="ei-row"
              onClick={() => { if (locked) return; setEditingId(r.id); }}
              title={locked ? "Published content is locked, only an Admin can edit it" : undefined}
              style={{ display: "grid", gridTemplateColumns: "minmax(220px,1.6fr) 130px 160px 150px 80px", gap: 14, alignItems: "center", padding: "12px 18px", cursor: locked ? "not-allowed" : "pointer", opacity: locked ? 0.6 : 1 }}>
              <div style={{ display: "flex", alignItems: "center", gap: 12, minWidth: 0 }}>
                <div className="thumb sm" style={{ backgroundImage: `url("${r.cover}")`, flexShrink: 0 }} />
                <div style={{ minWidth: 0 }}>
                  <div style={{ fontWeight: 700, fontSize: 14, color: "var(--text-primary)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{r.name}</div>
                  <div style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 2 }}>
                    <span style={{ fontSize: 12, color: "var(--gray-500)" }}>{r.category} · {r.duration} min</span>
                    <RefBadge kind="recipe" item={r} />
                  </div>
                </div>
              </div>
              <span className="tag" style={{ background: "var(--green-50)", color: "var(--green-700)", border: "1px solid var(--green-200)", fontSize: 11, justifySelf: "start" }}>{r.cuisine}</span>
              <span style={{ fontSize: 13, color: "var(--gray-600)" }}>{r.contributor.name}</span>
              <span style={{ justifySelf: "start" }}><StatusPill status={r.status} item={r} kind="recipe" /></span>
              {locked ?
                <span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 13, fontWeight: 700, color: "var(--gray-400)" }}><Icon name="lock" size={15} stroke={2} /> Locked</span> :
                <span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 13, fontWeight: 700, color: "var(--green-700)" }}><Icon name="pencil" size={15} stroke={2} /> Edit</span>}
            </div>
            );
          })}
      </div>
    </div>);
}

function normalizeAuthenticatedRole(value) {
  const normalized = String(value || "").trim().toLowerCase().replace(/_/g, "-").replace(/\s+/g, "-");
  const aliases = {
    "media-contributor": "media-contributor",
    reviewer: "reviewer",
    "editorial-manager": "manager",
    "compliance-officer": "compliance",
    admin: "admin",
    "super-admin": "super-admin",
  };
  const role = aliases[normalized] || normalized;
  return NAV_BY_ROLE[role] ? role : "";
}

function authenticatedAppRole() {
  const membership = window.__nutridmsActiveMembership;
  const identity = window.__nutridmsAuthenticatedUser;
  return normalizeAuthenticatedRole(
    (membership && membership.role && membership.role.key) ||
    (identity && identity.role) ||
    ""
  );
}

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "role": "media-contributor",
  "density": "regular",
  "theme": "light",
  "accent": "green",
  "lang": "en"
} /*EDITMODE-END*/;

const ROUTE_LANGUAGES = ["en", "es", "ja", "nl", "zh", "fr"];
function routeLanguage() {
  const requested = new URLSearchParams(window.location.search).get("locale");
  return ROUTE_LANGUAGES.includes(requested) ? requested : "";
}

/* Can this role open this page at all? Router already gates every screen with
   exactly this pair, so these are the authority -- not a second, hand-kept list.
   While entitlements are still loading, Entitlements.has() answers false for
   everything, so treat "not loaded" as unknown and keep the current page rather
   than bouncing the user home. */
function pageOpenableBy(page, role) {
  const entitlementsKnown = !window.Entitlements
    || typeof window.Entitlements.ready !== "function"
    || window.Entitlements.ready();
  if (entitlementsKnown && typeof pageEntitled === "function" && !pageEntitled(page, role)) return false;
  const permission = typeof pagePermissionKey === "function" ? pagePermissionKey(page) : null;
  if (permission && typeof permAllowed === "function" && !permAllowed(role, permission)) return false;
  return true;
}

/* Each workspace screen is addressable as its own route in the host app, which
   passes the screen down as ?page=<id>. Router() already falls back to the
   dashboard for an id it does not know, and applies the same entitlement and
   permission checks it applies to a sidebar click, so a deep link cannot reach
   anything a click could not. */
function routePage() {
  const requested = new URLSearchParams(window.location.search).get("page") || "";
  return /^[a-z][a-z0-9:-]{0,48}$/.test(requested) ? requested : "dashboard";
}

const RECIPE_LIBRARY_PAGES = new Set([
  "dashboard", "recipes", "published", "templates", "recipe-detail", "edit-recipe", "upload",
  "review-queue", "review-feedback", "urgent", "feedback", "nutrition-requests", "compliance-dashboard",
  "offerings", "meal-programs", "meal-program-builder", "meal-program-detail", "meal-planner",
  "restaurant-menus", "product-spec", "product-compliance", "fda-label", "nafdac-label", "label-studio",
  "nutrition-insights", "reports", "customer-mobile-view", "customer-portal", "customer-crm", "customer-dashboard"
]);
const INGREDIENT_LIBRARY_PAGES = new Set([
  "dashboard", "ingredients", "ingredient-detail", "create-ingredient", "edit-ingredient", "upload",
  "review-queue", "review-feedback", "urgent", "compliance-dashboard", "product-compliance", "fda-label",
  "nafdac-label", "label-studio", "offerings", "meal-programs", "meal-program-builder", "meal-program-detail",
  "meal-planner", "restaurant-menus", "product-spec", "nutrition-insights", "reports"
]);
const COMPLIANCE_PAGES = new Set([
  "compliance-dashboard", "compliance-rules", "health-tags", "allergen-rules", "health-conditions",
  "product-compliance", "fda-label", "nafdac-label", "label-studio", "review-queue", "review-feedback"
]);
const OFFERING_PAGES = new Set([
  "offerings", "offerings-list", "offering-detail", "product-spec", "product-compliance", "fda-label",
  "nafdac-label", "label-studio", "restaurant-menus", "meal-programs", "meal-program-builder", "meal-program-detail"
]);

function App() {
  const [tweaks, setTweak] = useTweaks ? useTweaks(TWEAK_DEFAULTS) : [TWEAK_DEFAULTS, () => {}];
  const [role, setRole] = aUseState(authenticatedAppRole() || tweaks.role || "media-contributor");
  const [lang, setLang] = aUseState(() => {
    const appearance = window.NutriAppearance && window.NutriAppearance.current ? window.NutriAppearance.current() : null;
    return routeLanguage() || (appearance && appearance.language) || tweaks.lang || "en";
  });
  const [page, setPage] = aUseState(routePage);
  const [activeRecipe, setActiveRecipe] = aUseState(null);
  const [activeIngredient, setActiveIngredient] = aUseState(null);
  const [reviewKind, setReviewKind] = aUseState("recipe");
  const [cmdOpen, setCmdOpen] = aUseState(false);
  const [notifOpen, setNotifOpen] = aUseState(false);
  const [quickAssign, setQuickAssign] = aUseState(null); // prefill object or null
  const [compWizard, setCompWizard] = aUseState(null); // {type,mode,row,onSaved} or null
  const [toastMsg, setToastMsg] = aUseState(null);
  const [, setAccessRevision] = aUseState(0);
  const [sidebarCollapsed, setSidebarCollapsed] = aUseState(() => localStorage.getItem("nutridms.app.sidebar.collapsed") === "1");
  const [mobileNav, setMobileNav] = aUseState(false);
  aUseEffect(() => {
    const params = new URLSearchParams(window.location.search);
    if (params.get("settings") === "security") {
      window.__settingsTab = "security";
      setPage("settings");
    }
  }, []);
  aUseEffect(() => { setMobileNav(false); }, [page]);
  aUseEffect(() => {
    window.__nutridmsActivePage = page;
    try { window.dispatchEvent(new CustomEvent("nutridms-page", { detail: { page: page } })); } catch (ignored) {}
    if (window.RecipeSync && typeof window.RecipeSync.setDemanded === "function") window.RecipeSync.setDemanded(RECIPE_LIBRARY_PAGES.has(page));
    if (window.IngredientSync && typeof window.IngredientSync.setDemanded === "function") window.IngredientSync.setDemanded(INGREDIENT_LIBRARY_PAGES.has(page));
    if (window.NutriComplianceSync && typeof window.NutriComplianceSync.demand === "function") window.NutriComplianceSync.demand(COMPLIANCE_PAGES.has(page));
    if (window.NutriOfferingsSync && typeof window.NutriOfferingsSync.demand === "function") window.NutriOfferingsSync.demand(OFFERING_PAGES.has(page));
  }, [page]);
  // Tell the host frame which screen is showing, so the URL keeps up with the
  // sidebar. The host rewrites the address bar; it never navigates, so this
  // does not reload the workspace.
  aUseEffect(() => {
    if (window.parent === window) return;
    try {
      window.parent.postMessage({ type: "nutridms-page-change", page: page }, window.location.origin);
    } catch (ignored) {}
  }, [page]);
  aUseEffect(() => {
    localStorage.setItem("nutridms.app.sidebar.collapsed", sidebarCollapsed ? "1" : "0");
    if (window.__nutridmsSessionReady && window.NutriAppearance && window.NutriAppearance.save) {
      window.NutriAppearance.save().catch(() => {});
    }
  }, [sidebarCollapsed]);
  const [navLayout, setNavLayout] = aUseState(() => localStorage.getItem("nutridms.nav.layout") || "sidebar");
  const [density2, setDensity2] = aUseState(() => localStorage.getItem("nutridms.appearance.density") || "");
  const NAV_COLORS = { "midnight": "linear-gradient(180deg, #3B7C0F 0%, #2E6109 100%)", "dark-green": "linear-gradient(180deg, #0E1612 0%, #0A100D 100%)", "white": "linear-gradient(180deg, #FFFFFF 0%, #F7F8F6 100%)", "cobalt": "linear-gradient(180deg, #2A54E5 0%, #1d3eb0 100%)", "violet": "linear-gradient(180deg, #6938EF 0%, #5022c0 100%)", "teal": "linear-gradient(180deg, #0E9384 0%, #0a6e63 100%)", "slate": "linear-gradient(180deg, #3a4757 0%, #2a3440 100%)" };
  const NAV_GLOWS = { "midnight": "rgba(105,159,42,.10)", "dark-green": "rgba(105,159,42,.10)", "white": "rgba(0,0,0,0)", "cobalt": "rgba(59,102,240,.18)", "violet": "rgba(124,82,245,.18)", "teal": "rgba(21,183,158,.18)", "slate": "rgba(148,163,184,.12)" };
  const [navColor, setNavColor] = aUseState(() => localStorage.getItem("nutridms.nav.color") || "dark-green");
  const [topbarColor, setTopbarColor] = aUseState(() => localStorage.getItem("nutridms.nav.topbarColor") || "white");
  const [topbarSync, setTopbarSync] = aUseState(() => localStorage.getItem("nutridms.nav.topbarSync") === "1");
  // CTA button color + page skin — platform-wide, available to everyone.
  const CTA_COLORS = { "green": ["#1B7528", "#15631F", "#268A38"], "forest": ["#0E5A2A", "#0A4720", "#137038"], "navy": ["#1E3A8A", "#172E6E", "#2549A8"], "teal": ["#0E9384", "#0a6e63", "#12A594"], "violet": ["#6938EF", "#5022c0", "#7C52F5"], "cobalt": ["#2A54E5", "#1d3eb0", "#3B66F0"], "slate": ["#334155", "#25303f", "#3f4d5e"], "amber": ["#B54708", "#8f3806", "#D6620E"] };
  const applyCta = (c) => { const v = CTA_COLORS[c] || CTA_COLORS.green; try { const r = document.documentElement; r.style.setProperty("--brand-700", v[0]); r.style.setProperty("--brand-800", v[1]); r.style.setProperty("--brand-600", v[2]); } catch (e) {} };
  const applySkin = (s) => { try { const el = document.querySelector(".app") || document.documentElement; el.setAttribute("data-skin", s || "default"); const dark = s === "dark-green" || s === "navy-green"; const base = localStorage.getItem("nutridms.theme") || "light"; const t = dark ? "dark" : base; document.documentElement.setAttribute("data-theme", t); el.setAttribute("data-theme", t); } catch (e) {} };
  aUseEffect(() => {
    // CTA color + page skin controls were removed — force platform defaults so no
    // previously-stored dark skin or custom button color stays stuck.
    try { localStorage.setItem("nutridms.skin", "default"); localStorage.removeItem("nutridms.cta.color"); } catch (e) {}
    applyCta("green");
    applySkin("default");
    const onApp = () => {setNavLayout(localStorage.getItem("nutridms.nav.layout") || "sidebar");setDensity2(localStorage.getItem("nutridms.appearance.density") || "");setNavColor(localStorage.getItem("nutridms.nav.color") || "dark-green");setTopbarColor(localStorage.getItem("nutridms.nav.topbarColor") || "white");setTopbarSync(localStorage.getItem("nutridms.nav.topbarSync") === "1");setSidebarCollapsed(localStorage.getItem("nutridms.app.sidebar.collapsed") === "1");applyCta(localStorage.getItem("nutridms.cta.color") || "green");applySkin(localStorage.getItem("nutridms.skin") || "default");};
    window.addEventListener("nutridms-appearance", onApp);
    return () => window.removeEventListener("nutridms-appearance", onApp);
  }, []);

  // Live navigation is derived from the authenticated membership, never from
  // device-local prototype state. Language follows the server-backed appearance.
  aUseEffect(() => {
    const syncRole = () => setRole(authenticatedAppRole() || tweaks.role || "media-contributor");
    syncRole();
    window.addEventListener("nutridms-backend", syncRole);
    return () => window.removeEventListener("nutridms-backend", syncRole);
  }, [tweaks.role]);
  aUseEffect(() => {
    const syncLanguage = () => {
      const appearance = window.NutriAppearance && window.NutriAppearance.current ? window.NutriAppearance.current() : null;
      setLang(routeLanguage() || (appearance && appearance.language) || tweaks.lang || "en");
    };
    syncLanguage();
    window.addEventListener("nutridms-appearance", syncLanguage);
    return () => window.removeEventListener("nutridms-appearance", syncLanguage);
  }, [tweaks.lang]);
  /* Access data arriving (or changing) only has to re-render: Router re-runs its
     own pageEntitled/permAllowed check on the current page and renders the
     "not included in plan" / "access not assigned" card in place.

     It deliberately does not navigate. `dashboard` is the one screen with
     neither an entitlement nor a permission key, so redirecting there on a
     negative answer sent the user home from almost any route -- including every
     time these events fire during a reload, while Entitlements.has() still
     answers false because the plan has not loaded. Leaving the URL alone keeps
     the route addressable and lets Router explain itself where the user is. */
  aUseEffect(() => {
    const refreshAccess = () => {
      setAccessRevision((value) => value + 1);
    };
    window.addEventListener("nutridms-perms", refreshAccess);
    window.addEventListener("nutridms-entitlements", refreshAccess);
    return () => {
      window.removeEventListener("nutridms-perms", refreshAccess);
      window.removeEventListener("nutridms-entitlements", refreshAccess);
    };
  }, []);
  /* A role change can leave the user on a screen the new role cannot open, so
     send them home when that happens.

     Deliberately does NOT run on mount. `role` boots from a placeholder until
     the session resolves, and running this against that placeholder discarded
     the deep-linked screen on every reload -- which is why reloading any
     workspace route landed back on /dashboard.

     It also no longer tests nav ids against a hand-kept allowlist. Plenty of
     real screens are reached by drilldown rather than a nav item (inventory
     sub-pages, the compliance tables, the customer portal, Loraa), and that
     list had drifted far enough to reset 26 of the 80 routed screens for every
     role, super-admin included. */
  const rolePrevRef = React.useRef(role);
  aUseEffect(() => {
    if (rolePrevRef.current === role) return;
    rolePrevRef.current = role;
    setActiveRecipe(null);
    if (!pageOpenableBy(page, role)) setPage("dashboard");
  }, [page, role]);

  // Keyboard ⌘K
  aUseEffect(() => {
    const onKey = (e) => {
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
        e.preventDefault();
        setCmdOpen(true);
      }
    };
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, []);

  const [detailFocus, setDetailFocus] = aUseState(null);
  const openRecipe = aUseCallback((r) => {setActiveRecipe(r);setPage("recipe-detail");}, []);
  const openIngredient = aUseCallback((i) => {setActiveIngredient(i);setPage("ingredient-detail");}, []);
  const openReview = aUseCallback((item, kind) => {
    setReviewKind(kind);
    if (kind === "ingredient") {setActiveIngredient(item);setActiveRecipe(null);} else
    {setActiveRecipe(item);setActiveIngredient(null);}
    setPage("review-feedback");
  }, []);
  const toast = aUseCallback((m) => setToastMsg(m), []);
  const openQuickAssign = aUseCallback((prefill) => setQuickAssign(prefill || {}), []);
  const openCompliance = aUseCallback((cfg) => setCompWizard(cfg || null), []);
  aUseEffect(() => { window.__openQuickAssign = openQuickAssign; }, [openQuickAssign]);
  // Expose for command palette
  aUseEffect(() => {window.__openRecipe = openRecipe;}, [openRecipe]);
  aUseEffect(() => {window.__openIngredient = openIngredient;}, [openIngredient]);
  aUseEffect(() => {window.__setPage = setPage;}, [setPage]);
  const prevPageRef = React.useRef("ingredients");
  aUseEffect(() => { window.__prevPage = prevPageRef.current; prevPageRef.current = page; }, [page]);
  aUseEffect(() => {window.__toast = toast;}, [toast]);

  // Cross-frame navigation from the create-iframes (e.g. Save as draft → library Drafts)
  aUseEffect(() => {
    const onMsg = (e) => {
      const d = e.data || {};
      if (d.type !== "nutridms-navigate") return;
      if (d.tab) window.__libInitialStatus = d.tab;
      if (d.settingsTab) window.__settingsTab = d.settingsTab;
      if (d.page) setPage(d.page);
      if (d.toast) setToastMsg(d.toast);
    };
    window.addEventListener("message", onMsg);
    return () => window.removeEventListener("message", onMsg);
  }, [setPage]);

  // Update role / lang -> persist via tweaks
  const setRoleAndPersist = (r) => {
    const liveRole = authenticatedAppRole();
    if (liveRole) {
      setRole(liveRole);
      return;
    }
    setRole(r);
    setTweak("role", r);
  };
  aUseEffect(() => {window.__setRole = setRoleAndPersist;}, []);
  aUseEffect(() => {window.__role = role;}, [role]);
  aUseEffect(() => {window.__openCmd = () => setCmdOpen(true);}, []);
  const setLangAndPersist = (l) => {
    setLang(l);
    if (window.NutriI18n) window.NutriI18n.apply(l);
    if (window.NutriAppearance && window.NutriAppearance.save && window.__nutridmsSessionReady) {
      const current = window.NutriAppearance.current ? window.NutriAppearance.current() : {};
      window.NutriAppearance.save({ ...current, language: l }).catch(() => {});
    } else {
      setTweak("lang", l);
    }
  };
  // Apply the active language across the whole platform on mount + whenever it changes.
  aUseEffect(() => {if(window.NutriI18n)window.NutriI18n.apply(lang);}, [lang]);

  const ctx = {
    role, setRole: setRoleAndPersist,
    lang, setLang: setLangAndPersist,
    page, setPage,
    activeRecipe, setActiveRecipe, openRecipe,
    activeIngredient, setActiveIngredient, openIngredient,
    reviewKind, setReviewKind, openReview,
    detailFocus, setDetailFocus,
    toast,
    openQuickAssign,
    openCompliance,
    openCmd: () => setCmdOpen(true),
    openNotif: () => setNotifOpen(true),
    sidebarCollapsed, setSidebarCollapsed,
    mobileNav, setMobileNav,
    navLayout, setNavLayout
  };

  return (
    <AppCtx.Provider value={ctx}>
      <div className={`app ${sidebarCollapsed ? "side-collapsed" : ""} ${mobileNav ? "mobile-nav-open" : ""} ${navColor === "white" ? "nav-white" : ""}`} data-density={density2 || tweaks.density} data-theme={tweaks.theme} data-accent={tweaks.accent} data-topbar-dark={(topbarSync ? navColor : topbarColor) !== "white" ? "1" : undefined} style={{ "--nav-bg": NAV_COLORS[navColor] || NAV_COLORS["dark-green"], "--rail-bg": NAV_COLORS[navColor] || NAV_COLORS["dark-green"], "--nav-glow": NAV_GLOWS[navColor] || NAV_GLOWS["dark-green"], "--sidebar-bg": navColor === "dark-green" ? "#11271b" : navColor === "white" ? "#FFFFFF" : (NAV_COLORS[navColor] || "").match(/#[0-9a-fA-F]{6}/)?.[0] || "#11271b", "--topbar-bg": NAV_COLORS[(topbarSync ? navColor : topbarColor)] || "#fff" }}>{/* nav */}
        <div className="mobile-nav-scrim" onClick={() => setMobileNav(false)}></div>
        <Sidebar />
        <main style={{ minWidth: 0 }}>
          <Topbar />
          <div className="page">
            <Router />
          </div>
        </main>
      </div>
      <CmdPalette open={cmdOpen} onClose={() => setCmdOpen(false)} />
      {window.WorkspaceDrawer ? <window.WorkspaceDrawer /> : null}
      <NotificationDrawer open={notifOpen} onClose={() => setNotifOpen(false)} />
      {quickAssign && window.QuickAssignDrawer && <window.QuickAssignDrawer prefill={quickAssign} onClose={() => setQuickAssign(null)} />}
      {compWizard && window.ComplianceWizardHost && <window.ComplianceWizardHost state={compWizard} onClose={() => setCompWizard(null)} />}
      <Toast msg={toastMsg} onDone={() => setToastMsg(null)} />
      <NutriTweaks tweaks={tweaks} setTweak={setTweak} />
    </AppCtx.Provider>);

}


/* ───────────────── Production onboarding clean slate ─────────────────
   The legacy design handoff contains seeded examples inside many feature
   modules. New production organizations must never see those records.
   Keep this router-level guard in place until a feature is backed by real
   organization-scoped data. Creation, import, people, settings and help stay
   available so the workspace can be built from authorized data only. */
if (typeof window !== "undefined") window.NUTRIDMS_PRODUCTION_CLEAN_SLATE = false;

const PRODUCTION_CLEAN_SLATE_PASSTHROUGH = new Set([
  "dashboard", "add-ingredient", "upload", "bulk-import",
  "users", "permissions", "settings", "integrations", "help"
]);

const PRODUCTION_EMPTY_STATES = {
  recipes: ["Recipe Library", "No recipes yet", "Recipes created or imported by your organization will appear here.", "utensils-crossed", "Create first recipe", "upload"],
  published: ["Published Recipes", "Nothing published yet", "Approved recipes will appear here after your team publishes them.", "send", "Create first recipe", "upload"],
  "recipe-detail": ["Recipe", "No recipe selected", "Create or import a recipe before opening recipe details.", "utensils-crossed", "Create first recipe", "upload"],
  ingredients: ["Ingredient Library", "No ingredients yet", "Build a trusted catalogue from your own ingredients. No sample ingredients are loaded.", "leaf", "Add first ingredient", "add-ingredient"],
  "ingredient-detail": ["Ingredient", "No ingredient selected", "Add or import an ingredient before opening ingredient details.", "leaf", "Add first ingredient", "add-ingredient"],
  templates: ["Templates", "No templates yet", "Templates created for your organization will appear here.", "layout-template", "Create first recipe", "upload"],
  "review-feedback": ["Review feedback", "No feedback yet", "Reviewer comments and requested changes will appear here.", "message-square", "Add first ingredient", "add-ingredient"],
  "my-questions": ["Questions", "No questions yet", "Questions raised by your team will appear here.", "message-circle-question", "Add first ingredient", "add-ingredient"],
  "nutrient-rules": ["Nutrient rules", "No organization rules yet", "Create rules only from requirements your organization has approved.", "list-checks", "Open settings", "settings"],
  "ingredient-rules": ["Ingredient rules", "No ingredient rules yet", "Your organization-specific ingredient rules will appear here.", "list-checks", "Open settings", "settings"],
  "health-tag-rules": ["Health tag rules", "No health tag rules yet", "Approved health tagging rules will appear here.", "tags", "Open settings", "settings"],
  "health-conditions": ["Health conditions", "No conditions configured", "Configure only the health conditions your organization is authorized to manage.", "heart-pulse", "Open settings", "settings"],
  "allergen-table": ["Allergen controls", "No allergen records yet", "Allergen controls will populate from your real ingredient and recipe data.", "shield-alert", "Add first ingredient", "add-ingredient"],
  "recipe-table": ["Recipe compliance", "No recipes to check", "Compliance results will appear after your first recipe is created.", "clipboard-check", "Create first recipe", "upload"],
  "nutrition-requests": ["Nutrition requests", "No requests yet", "New requests from your team will appear here.", "messages-square", "Add first ingredient", "add-ingredient"],
  "review-queue": ["Nutrition Review", "Nothing to review yet", "Submitted ingredients and recipes will enter this queue. No sample reviews are loaded.", "clipboard-check", "Add first ingredient", "add-ingredient"],
  urgent: ["Urgent Review", "No urgent reviews", "Items with a real urgent review status will appear here.", "siren", "Add first ingredient", "add-ingredient"],
  feedback: ["Feedback", "No feedback yet", "Feedback on real workspace records will appear here.", "message-square", "Create first recipe", "upload"],
  analytics: ["Analytics", "No analytics yet", "Charts and performance metrics will begin after your organization creates real activity.", "chart-no-axes-combined", "Create first recipe", "upload"],
  reports: ["Reports", "No reports yet", "Reports are generated only from your organization's real records.", "file-chart-column", "Create first recipe", "upload"],
  calendar: ["Publishing Calendar", "Nothing scheduled yet", "Approved recipes, ingredients and menus will appear here when they are scheduled.", "calendar-days", "Create first recipe", "upload"],
  assignments: ["Assignments", "No assignments yet", "Create content and invite your team before assigning work.", "kanban-square", "Invite a teammate", "users"],
  "create-assignment": ["Assignments", "Nothing available to assign", "Create an ingredient or recipe first, then assign it to a teammate.", "user-round-plus", "Create first recipe", "upload"],
  "my-assignments": ["My Assignments", "Nothing assigned to you", "Work assigned to your account will appear here.", "user-check", "Go to dashboard", "dashboard"],
  audit: ["Audit Log", "No audit events yet", "Real account, content and workflow activity will be recorded here.", "history", "Go to dashboard", "dashboard"],
  "loraa-analytics": ["Loraa Analytics", "No Loraa activity yet", "Loraa analytics will use only your organization's real workspace activity.", "sparkles", "Add first ingredient", "add-ingredient"],
  "customer-mobile-view": ["Customer mobile view", "No customer experience yet", "Publish real organization content before creating a customer view.", "smartphone", "Create first recipe", "upload"],
  "customer-portal": ["Customer portal", "No portal content yet", "Only content approved by your organization will appear in the customer portal.", "panels-top-left", "Create first recipe", "upload"],
  "customer-crm": ["Customer CRM", "No customers yet", "Customer records will appear only after your organization connects an authorized source.", "contact-round", "Configure integrations", "integrations"],
  "customer-qr": ["Customer QR", "No QR experiences yet", "Create approved customer content before generating QR experiences.", "qr-code", "Create first recipe", "upload"],
  "customer-dashboard": ["Customer dashboard", "No customer data yet", "Customer metrics will appear after real customer activity begins.", "chart-no-axes-combined", "Configure integrations", "integrations"],
  "verify-queue": ["Ingredient verification", "Nothing to verify yet", "Ingredients submitted by your organization will appear here.", "badge-check", "Add first ingredient", "add-ingredient"],
  "label-studio": ["Label Studio", "No labels yet", "Create labels from verified organization ingredients and recipes.", "tags", "Add first ingredient", "add-ingredient"],
  "fop-compliance": ["Front-of-pack compliance", "Nothing to check yet", "Compliance checks will appear after real product data is added.", "shield-check", "Add first ingredient", "add-ingredient"],
  "fda-label": ["FDA labels", "No labels yet", "FDA label projects will appear after real product data is added.", "badge-check", "Add first ingredient", "add-ingredient"],
  "nafdac-label": ["NAFDAC labels", "No labels yet", "NAFDAC label projects will appear after real product data is added.", "badge-check", "Add first ingredient", "add-ingredient"],
  "nutrition-insights": ["Nutrition insights", "No insights yet", "Insights will be calculated from your organization's verified data only.", "chart-spline", "Add first ingredient", "add-ingredient"],
  offerings: ["Offerings", "No offerings yet", "Create a verified recipe before building an offering.", "boxes", "Create first recipe", "upload"],
  supplements: ["Supplement Studio", "No supplements yet", "Supplement records created by your organization will appear here.", "pill", "Add first ingredient", "add-ingredient"],
  "restaurant-menus": ["Restaurant Menus", "No menus yet", "Build menus from approved recipes and offerings. No sample menus are loaded.", "book-open", "Create first recipe", "upload"],
  inventory: ["Inventory & Production", "No inventory yet", "Receive your organization's first real ingredient or packaging item to begin.", "package-open", "Add first ingredient", "add-ingredient"],
  gs1: ["GS1 & Barcodes", "No GTINs or barcodes yet", "GS1 assignments and validation activity will appear only after you add real products.", "scan-barcode", "Create first recipe", "upload"],
  "compliance-dashboard": ["Compliance", "No compliance activity yet", "Results will populate from your organization's real ingredients, recipes and labels.", "shield-check", "Add first ingredient", "add-ingredient"],
  "ask-laura": ["Loraa Command Center", "Loraa is ready for real data", "Add or import your first ingredient or recipe. Loraa will not invent sample insights, alerts or actions.", "sparkles", "Add first ingredient", "add-ingredient"],
  "qa-review": ["Loraa QA Review", "No QA reviews yet", "Loraa will review only real meal-program data created by your organization.", "sparkles", "Create first recipe", "upload"],
  "meal-programs": ["Meal Programs", "No meal programs yet", "Start with a verified recipe, then build your organization's first program.", "utensils", "Create first recipe", "upload"],
  "meal-planner": ["Meal Planner", "Nothing planned yet", "Planning starts after your organization creates its first meal program.", "calendar-range", "Create first recipe", "upload"],
  "meal-program-detail": ["Meal Program", "No program selected", "Create a real meal program before opening program details.", "layers", "Create first recipe", "upload"],
  "meal-program-builder": ["Meal Program Builder", "No source content yet", "Add a verified recipe before building your first meal program.", "square-plus", "Create first recipe", "upload"]
};

function productionEmptyStateFor(page) {
  if (!window.NUTRIDMS_PRODUCTION_CLEAN_SLATE || !page || PRODUCTION_CLEAN_SLATE_PASSTHROUGH.has(page)) return null;
  if (PRODUCTION_EMPTY_STATES[page]) return PRODUCTION_EMPTY_STATES[page];
  if (String(page).indexOf("reports:") === 0) return PRODUCTION_EMPTY_STATES.reports;
  if (String(page).indexOf("inv-") === 0) return PRODUCTION_EMPTY_STATES.inventory;
  if (String(page).indexOf("intl-label:") === 0) return PRODUCTION_EMPTY_STATES["label-studio"];
  return ["Workspace", "Nothing here yet", "This feature starts empty. Only records created or connected by your organization will appear here.", "inbox", "Go to dashboard", "dashboard"];
}

function ProductionEmptyState({ page, config }) {
  const { setPage } = useApp();
  const [eyebrow, title, description, icon, actionLabel, actionPage] = config;
  return (
    <section aria-labelledby="production-empty-title" style={{ maxWidth: 1320, margin: "0 auto", padding: "18px 0 48px" }}>
      <div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 24, flexWrap: "wrap", marginBottom: 28 }}>
        <div>
          <div style={{ display: "inline-flex", alignItems: "center", gap: 8, color: "var(--green-700, #187b28)", fontSize: 12, fontWeight: 800, letterSpacing: ".11em", textTransform: "uppercase", marginBottom: 10 }}>
            <span style={{ width: 8, height: 8, borderRadius: 999, background: "#39a944" }} />
            Clean workspace
          </div>
          <h1 id="production-empty-title" style={{ fontFamily: "var(--serif)", fontSize: "clamp(34px, 4vw, 52px)", lineHeight: 1.05, margin: 0, color: "var(--ink, #101827)" }}>{eyebrow}</h1>
          <p style={{ margin: "12px 0 0", color: "var(--muted, #687386)", fontSize: 16, maxWidth: 720, lineHeight: 1.6 }}>{description}</p>
        </div>
        <div style={{ display: "inline-flex", alignItems: "center", gap: 9, border: "1px solid #dce5de", background: "#fff", borderRadius: 999, padding: "10px 14px", color: "#42614b", fontWeight: 700, fontSize: 13 }}>
          <Icon name="shield-check" size={16} />
          Organization data only
        </div>
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))", gap: 16, marginBottom: 18 }}>
        {[
          ["Records", "0", "No sample records"],
          ["Pending", "0", "No work waiting"],
          ["Recent activity", "0", "Ready for real events"]
        ].map(([label, value, note]) => (
          <div key={label} className="card" style={{ padding: "22px 24px", borderRadius: 18, minHeight: 118 }}>
            <div style={{ color: "#687386", fontSize: 13, marginBottom: 10 }}>{label}</div>
            <div style={{ fontFamily: "var(--serif)", fontSize: 34, lineHeight: 1, fontWeight: 800, marginBottom: 8 }}>{value}</div>
            <div style={{ color: "#879286", fontSize: 12 }}>{note}</div>
          </div>
        ))}
      </div>

      <div className="card" style={{ minHeight: 360, borderRadius: 22, display: "grid", placeItems: "center", padding: "46px 28px", textAlign: "center", border: "1px dashed #cad8cd", background: "linear-gradient(180deg, #ffffff 0%, #fbfdf9 100%)" }}>
        <div style={{ maxWidth: 590 }}>
          <div style={{ width: 78, height: 78, borderRadius: 24, display: "grid", placeItems: "center", margin: "0 auto 20px", color: "#27821f", background: "#effbe7", border: "1px solid #d3efbf" }}>
            <Icon name={icon} size={35} stroke={1.8} />
          </div>
          <h2 style={{ fontFamily: "var(--serif)", fontSize: 30, lineHeight: 1.15, margin: "0 0 11px", color: "var(--ink, #101827)" }}>{title}</h2>
          <p style={{ color: "var(--muted, #687386)", fontSize: 15, lineHeight: 1.65, margin: "0 auto 25px", maxWidth: 520 }}>{description}</p>
          <div style={{ display: "flex", justifyContent: "center", gap: 10, flexWrap: "wrap" }}>
            <button className="btn primary" onClick={() => setPage(actionPage)}><Icon name="plus" size={16} /> {actionLabel}</button>
            {actionPage !== "bulk-import" && (
              <button className="btn secondary" onClick={() => setPage("bulk-import")}><Icon name="upload-cloud" size={16} /> Import authorized data</button>
            )}
          </div>
          <div style={{ marginTop: 20, color: "#738077", fontSize: 12.5 }}>
            NutriDMS never preloads demo business data into a production organization.
          </div>
        </div>
      </div>
    </section>
  );
}

function Router() {
  const { page, role } = useApp();
  const requiredFeature = typeof pageEntitlementKey === "function" ? pageEntitlementKey(page) : null;
  if (requiredFeature && typeof pageEntitled === "function" && !pageEntitled(page, role)) {
    const activePlan = window.Entitlements && window.Entitlements.plan ? window.Entitlements.plan() : "starter";
    return (
      <div className="card pad" style={{ maxWidth: 620, margin: "56px auto", textAlign: "center" }}>
        <div className="stat-icon" style={{ margin: "0 auto 14px" }}><Icon name="lock-keyhole" size={24} /></div>
        <h2 style={{ margin: 0 }}>Not included in {String(activePlan).replace(/^./, (value) => value.toUpperCase())}</h2>
        <p className="muted">This workspace plan does not include this feature. Change the subscription or activate its paid add-on to continue.</p>
      </div>
    );
  }
  const requiredPermission = typeof pagePermissionKey === "function" ? pagePermissionKey(page) : null;
  if (requiredPermission && typeof permAllowed === "function" && !permAllowed(role, requiredPermission)) {
    return (
      <div className="card pad" style={{ maxWidth: 620, margin: "56px auto", textAlign: "center" }}>
        <div className="stat-icon" style={{ margin: "0 auto 14px" }}><Icon name="shield-x" size={24} /></div>
        <h2 style={{ margin: 0 }}>Access not assigned</h2>
        <p className="muted">Your current role or personal access policy does not include this area.</p>
      </div>
    );
  }
  const productionEmptyState = productionEmptyStateFor(page);
  if (productionEmptyState) return <ProductionEmptyState page={page} config={productionEmptyState} />;
  if (page === "dashboard") return <Dashboard />;
  if (page === "recipes") return <RecipesList />;
  if (page === "published") return <PublishedRecipes />;
  if (page === "recipe-detail") return <RecipeDetail />;
  if (page === "ingredient-detail") return <IngredientDetail />;
  if (page === "review-feedback") return <ReviewFeedback />;
  if (page === "upload") return <CreationGate kind="recipe"><UploadWizard /></CreationGate>;
  if (page === "templates") return <TemplatesPage />;
  if (page === "add-ingredient") return <AddIngredientPage />;
  if (page === "edit-ingredient") return <EditIngredientPage />;
  if (page === "edit-recipe") return <EditRecipePage />;
  if (page === "my-questions") return <MyQuestionsPage />;
  if (page === "nutrient-rules") return <NutrientRulesPage />;
  if (page === "ingredient-rules") return <IngredientRulesPage />;
  if (page === "health-tag-rules") return <HealthTagRulesPage />;
  if (page === "health-conditions") return <HealthConditionsPage />;
  if (page === "allergen-table") return <AllergenTablePage />;
  if (page === "recipe-table") return <ComplianceRecipeTablePage />;
  if (page === "nutrition-requests") return <DietitianRequestsPage />;
  if (page === "ingredients") return <IngredientsLibraryPage />;
  if (page === "review-queue") return role === "media-contributor" ? <ReviewQueue /> : <NutritionReview />;
  if (page === "feedback") return <FeedbackPage />;
  if (page === "users") return <UsersScreen />;
  if (page === "permissions") return <PermissionsScreen />;
  if (page === "bulk-import") return <BulkImportScreen />;
  if (page === "settings") return <SettingsScreen />;
  if (page === "analytics") return <AnalyticsScreen />;
  if (page === "reports" || (typeof page === "string" && page.startsWith("reports:"))) {
    // Restaurant Portal is permanently locked — no direct-route access for anyone.
    if (typeof restaurantPortalEnabled === "function" && !restaurantPortalEnabled()) return <Dashboard />;
    return <ReportsModule page={page} />;
  }
  if (page === "calendar") return <PublishingCalendar />;
  if (page === "assignments") return <AssignmentsScreen />;
  if (page === "create-assignment") return <CreateAssignmentPage />;
  if (page === "my-assignments") return <MyAssignments />;
  if (page === "audit") return <AuditLog />;
  if (page === "loraa-analytics") return <AuditLog />;
  if (page === "customer-mobile-view" && window.CustomerMobileView) return React.createElement(window.CustomerMobileView);
  if (page === "customer-portal" && window.CustomerPortalHub) return React.createElement(window.CustomerPortalHub);
  if (page === "customer-crm" && window.CustomerCRM) return React.createElement(window.CustomerCRM);
  if (page === "customer-qr" && window.CustomerQR) return React.createElement(window.CustomerQR);
  if (page === "customer-dashboard" && window.CustomerDashboard) return React.createElement(window.CustomerDashboard);
  if (page === "verify-queue" && window.IngredientVerifStaff) return React.createElement(window.IngredientVerifStaff);
  if (page === "label-studio") return <LabelStudio />;  if (page === "fop-compliance") return <FopComplianceEngine />;  if (page === "fda-label") return <FdaLabelStudio />;  if (page === "nafdac-label") return <NafdacLabelStudio />;  if (typeof page === "string" && page.indexOf("intl-label:") === 0) return <IntlLabelComingSoon market={page.split(":")[1]} />;  if (page === "nutrition-insights") return <NutritionInsightEngine />;  if (page === "integrations") return <IntegrationsScreen />;
  if (page === "offerings") return <OfferingsScreen />;
  if (page === "supplements" && window.SupplementStudio) return React.createElement(window.SupplementStudio);
  if (page === "restaurant-menus" && window.RestaurantMenuBuilder) return React.createElement(window.RestaurantMenuBuilder);
  if ((page === "inventory" || (page || "").indexOf("inv-") === 0) && window.InventoryWorkspace) return React.createElement(window.InventoryWorkspace);
  if (page === "gs1") return <Gs1Barcodes />;
  if (page === "compliance-dashboard") return <ComplianceDashboard />;
  if (page === "ask-laura") return <LoraaCommandCenterRoute />;
  if (page === "meal-programs") return <MealPrograms />;
  if (page === "meal-planner") return <MealPlannerScreen />;
  if (page === "meal-program-detail") return <MealProgramDetail />;
  if (page === "meal-program-builder") return <MealProgramBuilder />;
  if (page === "qa-review") return <LoraaCommandCenterRoute />;
  if (page === "urgent") return role === "media-contributor" ? <ReviewQueue /> : <NutritionReview initialScope="urgent" />;
  if (page === "help") return <HelpDocsScreen />;
  return <Dashboard />;
}

function NutriTweaks({ tweaks, setTweak }) {
  if (!window.TweaksPanel) return null;
  return (
    <TweaksPanel title="NutriDMS Tweaks">
      <TweakSection label="Role">
        <TweakSelect
          label="Active role"
          value={tweaks.role}
          onChange={(v) => setTweak("role", v)}
          options={Object.entries(ROLES).map(([id, r]) => ({ value: id, label: r.label }))} />
        
      </TweakSection>

      <TweakSection label="Display">
        <TweakRadio
          label="Theme"
          value={tweaks.theme}
          onChange={(v) => setTweak("theme", v)}
          options={[{ value: "light", label: "Light" }, { value: "dark", label: "Dark" }]} />
        
        <TweakRadio
          label="Density"
          value={tweaks.density}
          onChange={(v) => setTweak("density", v)}
          options={[{ value: "regular", label: "Regular" }, { value: "comfortable", label: "Roomy" }, { value: "compact", label: "Compact" }]} />
        
      </TweakSection>

      <TweakSection label="Locale">
        <TweakSelect
          label="Display language"
          value={tweaks.lang}
          onChange={(v) => setTweak("lang", v)}
          options={LANGUAGES.map((l) => ({ value: l.code, label: `${l.flag} ${l.label}` }))} />
        
      </TweakSection>
    </TweaksPanel>);

}

/* ─────────────────────────────────────────────────────────────
   Doc preview mode: renders ONE live screen with no sidebar/topbar,
   frozen (non-interactive), so Help & Docs can embed always-current
   "exact screenshots" of the real app via an <iframe>.
   URL: index.html?docpreview=<pageId>&role=<roleId>
   ───────────────────────────────────────────────────────────── */
function DocPreviewApp({ pageId, role }) {
  const noop = () => {};
  aUseEffect(() => { window.__role = role; document.documentElement.classList.add("is-doc-preview"); }, [role]);
  const ctx = {
    role, setRole: noop,
    lang: "en", setLang: noop,
    page: pageId, setPage: noop,
    activeRecipe: null, setActiveRecipe: noop, openRecipe: noop,
    activeIngredient: null, setActiveIngredient: noop, openIngredient: noop,
    reviewKind: "recipe", setReviewKind: noop, openReview: noop,
    detailFocus: null, setDetailFocus: noop,
    toast: noop,
    openQuickAssign: noop,
    openCompliance: noop,
    openCmd: noop,
    openNotif: noop,
    sidebarCollapsed: false, setSidebarCollapsed: noop,
    mobileNav: false, setMobileNav: noop,
    navLayout: "sidebar", setNavLayout: noop,
  };
  return (
    <AppCtx.Provider value={ctx}>
      <div className="app doc-preview-app" data-theme="light" data-density="regular">
        <main style={{ minWidth: 0 }}>
          <div className="page">
            <Router />
          </div>
        </main>
      </div>
    </AppCtx.Provider>);
}

// Mount
(function () {
  const params = new URLSearchParams(location.search);
  const dp = params.get("docpreview");
  const root = ReactDOM.createRoot(document.getElementById("root"));
  if (dp) {
    root.render(<DocPreviewApp pageId={dp} role={params.get("role") || "admin"} />);
    // Allow batch capture to switch pages without a reload
    window.__dpGo = (pid, r) => root.render(<DocPreviewApp pageId={pid} role={r || params.get("role") || "admin"} />);
  } else {
    const announceWorkspaceReady = () => {
      window.requestAnimationFrame(() => {
        window.requestAnimationFrame(() => {
          try {
            if (window.parent !== window) {
              window.parent.postMessage({ type: "nutridms-workspace-ready" }, window.location.origin);
            }
          } catch (_) {}
        });
      });
    };
    root.render(<App />);
    if (window.__nutridmsSessionReady) {
      announceWorkspaceReady();
    } else {
      window.addEventListener("nutridms-backend", announceWorkspaceReady, { once: true });
    }
  }
})();
