/* NutriDMS, shared components */
const { useState, useEffect, useRef, useMemo, useCallback, createContext, useContext } = React;

// ───────────────── Icon (renders Lucide SVGs directly, no DOM mutation) ─────────────────
// Lucide-ready signal: the CDN can load after React's first render (or be slow /
// flaky on "latest"). Icons subscribe so they re-render the moment it arrives,
// instead of getting stuck as empty boxes.
let __lucideReady = !!(typeof window !== "undefined" && window.lucide && window.lucide.icons);
const __lucideSubs = new Set();
if (typeof window !== "undefined" && !__lucideReady) {
  const __lc = setInterval(() => {
    if (window.lucide && window.lucide.icons) { __lucideReady = true; clearInterval(__lc); __lucideSubs.forEach((fn) => { try { fn(); } catch (e) {} }); }
  }, 60);
  setTimeout(() => clearInterval(__lc), 10000);
}
function Icon({ name, size = 18, stroke = 1.7, className = "", style = {} }) {
  const [, __force] = React.useReducer((x) => x + 1, 0);
  React.useEffect(() => {
    if (__lucideReady) return;
    __lucideSubs.add(__force);
    return () => __lucideSubs.delete(__force);
  }, []);
  const key = name.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
  const data = (window.lucide && window.lucide.icons && (window.lucide.icons[key] || window.lucide.icons[name]))
    || (window.__ICON_FB && (window.__ICON_FB[key] || window.__ICON_FB[name]))
    || null;
  if (!data) {
    return <span style={{ display: "inline-block", width: size, height: size, ...style }} className={className} />;
  }
  // Lucide v0.471+ UMD format: data is array of [tagName, attrsObject] tuples
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width={size}
      height={size}
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth={stroke}
      strokeLinecap="round"
      strokeLinejoin="round"
      className={className}
      style={{ display: "inline-block", flexShrink: 0, verticalAlign: "middle", ...style }}>
      
      {data.map(([tag, attrs], i) => React.createElement(tag, { key: i, ...attrs }))}
    </svg>);

}

// ───────────────── Brand ─────────────────
function BrandMark({ size = 32 }) {
  const [logo, setLogo] = React.useState(() => { try { return localStorage.getItem("nutridms_org_logo") || ""; } catch (e) { return ""; } });
  React.useEffect(() => {
    const h = () => { try { setLogo(localStorage.getItem("nutridms_org_logo") || ""); } catch (e) {} };
    window.addEventListener("nutridms-org", h);
    window.addEventListener("storage", h);
    return () => { window.removeEventListener("nutridms-org", h); window.removeEventListener("storage", h); };
  }, []);
  if (logo) {
    return (
      <div className="brand-tile brand-tile-custom" style={{ width: size, height: size, borderRadius: size * 0.265, backgroundImage: `url("${logo}")` }} aria-label="Organization logo" />);
  }
  return (
    <div className="brand-tile" style={{ width: size, height: size, borderRadius: size * 0.265 }}>
      <img src="/brand/nutridms-mark.svg" alt="NutriDMS" width={Math.round(size * 0.66)} height={Math.round(size * 0.66)} style={{ display: "block", objectFit: "contain" }} />
    </div>);

}

/* Official NutriDMS lockup */
function NutriBrandLogo() {
  return (
    <div className="brand-logo">
      <img src="/brand/nutridms-logo-dark.svg" alt="NutriDMS, Smart Nutrition Management" style={{ height: "48px", width: "auto", display: "block", objectFit: "contain" }} />
    </div>);
}

// ───────────────── App context ─────────────────
const AppCtx = createContext(null);
const useApp = () => useContext(AppCtx);

const ROLE_SWITCH_META = {
  "media-contributor": { icon: "pencil", accent: "green" },
  "reviewer": { icon: "eye", accent: "blue" },
  "manager": { icon: "book-open", accent: "violet" },
  "compliance": { icon: "clipboard-list", accent: "amber" },
  "admin": { icon: "settings", accent: "pink" },
  "super-admin": { icon: "zap", accent: "lime" },
};
const STATUS_PILL = {
  "draft": { tone: "neutral", label: "Draft", icon: "edit-3" },
  "pending-review": { tone: "warning", label: "Pending Review", icon: "clock" },
  "compliance-review": { tone: "info", label: "Compliance Review", icon: "shield-check" },
  "changes-requested": { tone: "violet", label: "Changes Requested", icon: "message-square" },
  "approved": { tone: "success", label: "Approved", icon: "check-circle-2" },
  "published": { tone: "brand", label: "Published", icon: "globe" },
  "rejected": { tone: "error", label: "Rejected", icon: "x-circle" },
  "awaiting-attention": { tone: "warning", label: "Awaiting Attention", icon: "alert-circle" }
};
function StatusPill({ status, item, kind }) {
  const s = STATUS_PILL[status] || { tone: "neutral", label: status, icon: "circle" };
  // When an item + kind are supplied, show the exact CONFIGURED workflow step
  // name (e.g. "Pending Editorial Manager") instead of the generic family label,
  // while keeping the same tone/icon so filters and colors stay consistent.
  let label = s.label;
  try {
    if (item && kind && (status === "pending-review" || status === "compliance-review") && window.weFlow) {
      const flow = window.weFlow(item, kind, "manager");
      if (flow && flow.phase === "review" && flow.currentStep && flow.currentStep.name) {
        label = "Pending " + flow.currentStep.name;
      }
    }
  } catch (e) { /* fall back to generic label */ }
  return (
    <span className={`pill ${s.tone}`} title={label}>
      <Icon name={s.icon} size={12} stroke={2} />
      {label}
    </span>);

}
function PriorityPill({ priority }) {
  const map = { high: "error", medium: "warning", low: "neutral" };
  return <span className={`pill ${map[priority] || "neutral"}`} style={{ textTransform: "capitalize", justifyContent: "center", minWidth: 76 }}>{priority}</span>;
}

/* Audit reference badge, stable, copyable ID shown on every recipe/ingredient.
   kind: "recipe" | "ingredient". Pass either the item or its id.
   size: "sm" (lists) | "md" (detail headers). */
function RefBadge({ kind, item, id, size, title }) {
  const ref = (window.auditRef ? window.auditRef(kind, item || id) : null);
  const app = (typeof useApp === "function") ? useApp() : null;
  if (!ref) return null;
  const copy = (e) => {
    e.stopPropagation(); e.preventDefault();
    try { navigator.clipboard && navigator.clipboard.writeText(ref); } catch (err) {}
    if (app && app.toast) app.toast(`Reference ${ref} copied`);
  };
  return (
    <button type="button" className={`ref-badge ${size === "md" ? "md" : ""}`} onClick={copy}
      title={title || `${kind === "ingredient" ? "Ingredient" : "Recipe"} reference, click to copy`}>
      <Icon name="hash" size={size === "md" ? 13 : 11} stroke={2.4} />
      <span className="ref-badge-num">{ref}</span>
      <Icon name="copy" size={size === "md" ? 12 : 10} stroke={2.2} className="ref-badge-copy" />
    </button>);
}

// ───────────────── Sidebar ─────────────────
const NAV_BY_ROLE = {
  "media-contributor": [
  { section: "DASHBOARD", items: [
    { id: "dashboard", icon: "layout-dashboard", label: "Dashboard" }]
  },
  { section: "PRODUCTS", items: [
    { id: "add-ingredient", icon: "leaf", label: "Add Ingredient", children: [
      { id: "add-ingredient", icon: "plus", label: "New Ingredient" },
      { id: "edit-ingredient", icon: "pencil", label: "Edit Ingredient" }]
    },
    { id: "create-recipe", icon: "upload-cloud", label: "Create New Recipe", children: [
      { id: "upload", icon: "plus", label: "New Recipe" },
      { id: "edit-recipe", icon: "pencil", label: "Edit Recipe" }]
    },
    { id: "library", icon: "book-open", label: "Library", children: [
      { id: "recipes", icon: "utensils-crossed", label: "Published Recipes", count: 6 },
      { id: "ingredients", icon: "leaf", label: "Published Ingredients" },
      { id: "review-queue", icon: "clipboard-check", label: "Review Queue" },
      { id: "my-questions", icon: "message-circle-question", label: "My Questions" }]
    }]
  },
  { section: "OPERATIONS", items: [
    { id: "calendar", icon: "calendar-days", label: "Publishing Calendar" },
    { id: "team-board", icon: "kanban-square", label: "Team Board", children: [
      { id: "create-assignment", icon: "plus", label: "Create Assignment" },
      { id: "my-assignments", icon: "user-check", label: "My Assignments" },
      { id: "assignments", icon: "kanban-square", label: "Team Board" }]
    }]
  },
  { section: "ADMINISTRATION", items: [
    { id: "settings", icon: "settings", label: "Settings" },
    { id: "help", icon: "life-buoy", label: "Help & Docs", count: 2 }]
  }],

  "reviewer": [
  { section: "DASHBOARD", items: [
    { id: "dashboard", icon: "layout-dashboard", label: "Dashboard" }]
  },
  { section: "PRODUCTS", items: [
    { id: "add-ingredient", icon: "leaf", label: "Add Ingredient", children: [
      { id: "add-ingredient", icon: "plus", label: "New Ingredient" },
      { id: "edit-ingredient", icon: "pencil", label: "Edit Ingredient" }]
    },
    { id: "create-recipe", icon: "upload-cloud", label: "Create New Recipe", children: [
      { id: "upload", icon: "plus", label: "New Recipe" },
      { id: "edit-recipe", icon: "pencil", label: "Edit Recipe" }]
    },
    { id: "library", icon: "book-open", label: "Library", children: [
      { id: "recipes", icon: "utensils-crossed", label: "Submitted Recipes" },
      { id: "ingredients", icon: "leaf", label: "Submitted Ingredients" },
      { id: "review-queue", icon: "clipboard-check", label: "Review Queue" },
      { id: "my-questions", icon: "message-circle-question", label: "My Questions" }]
    }]
  },
  { section: "COMPLIANCE", items: [
    { id: "nutrition-requests", icon: "messages-square", label: "Nutrition Requests" }]
  },
  { section: "OPERATIONS", items: [
    { id: "calendar", icon: "calendar-days", label: "Publishing Calendar" },
    { id: "team-board", icon: "kanban-square", label: "Team Board", children: [
      { id: "create-assignment", icon: "plus", label: "Create Assignment" },
      { id: "my-assignments", icon: "user-check", label: "My Assignments" },
      { id: "assignments", icon: "kanban-square", label: "Team Board" }]
    }]
  },
  { section: "ADMINISTRATION", items: [
    { id: "settings", icon: "settings", label: "Settings" }]
  }],

  "manager": [
  { section: "DASHBOARD", items: [
    { id: "dashboard", icon: "layout-dashboard", label: "Dashboard" }]
  },
  { section: "PRODUCTS", items: [
    { id: "add-ingredient", icon: "leaf", label: "Add Ingredient", children: [
      { id: "add-ingredient", icon: "plus", label: "New Ingredient" },
      { id: "edit-ingredient", icon: "pencil", label: "Edit Ingredient" }]
    },
    { id: "create-recipe", icon: "upload-cloud", label: "Create New Recipe", children: [
      { id: "upload", icon: "plus", label: "New Recipe" },
      { id: "edit-recipe", icon: "pencil", label: "Edit Recipe" }]
    },
    { id: "library", icon: "book-open", label: "Library", children: [
      { id: "recipes", icon: "utensils-crossed", label: "Submitted Recipes" },
      { id: "ingredients", icon: "leaf", label: "Submitted Ingredients" },
      { id: "my-questions", icon: "message-circle-question", label: "My Questions" }]
    }]
  },
  { section: "COMPLIANCE", items: [
    { id: "review-queue", icon: "clipboard-check", label: "Nutrition Review", count: 4 },
    { id: "nutrition-requests", icon: "messages-square", label: "Nutrition Requests" },
    { id: "verify-queue", icon: "microscope", label: "Verification Queue" }]
  },
  { section: "OPERATIONS", items: [
    { id: "calendar", icon: "calendar-days", label: "Publishing Calendar" },
    { id: "team-board", icon: "kanban-square", label: "Team Board", children: [
      { id: "create-assignment", icon: "plus", label: "Create Assignment" },
      { id: "my-assignments", icon: "user-check", label: "My Assignments" },
      { id: "assignments", icon: "kanban-square", label: "Team Board" }]
    }]
  },
  { section: "ADMINISTRATION", items: [
    { id: "users", icon: "users", label: "People" },
    { id: "settings", icon: "settings", label: "Settings" }]
  }],

  "compliance": [
  { section: "DASHBOARD", items: [
    { id: "dashboard", icon: "layout-dashboard", label: "Dashboard" }]
  },
  { section: "PRODUCTS", items: [
    { id: "add-ingredient", icon: "leaf", label: "Add Ingredient", children: [
      { id: "add-ingredient", icon: "plus", label: "New Ingredient" },
      { id: "edit-ingredient", icon: "pencil", label: "Edit Ingredient" }]
    },
    { id: "create-recipe", icon: "upload-cloud", label: "Create New Recipe", children: [
      { id: "upload", icon: "plus", label: "New Recipe" },
      { id: "edit-recipe", icon: "pencil", label: "Edit Recipe" }]
    },
    { id: "library", icon: "book-open", label: "Library", children: [
      { id: "recipes", icon: "utensils-crossed", label: "Submitted Recipes" },
      { id: "ingredients", icon: "leaf", label: "Submitted Ingredients" },
      { id: "my-questions", icon: "message-circle-question", label: "My Questions" }]
    },
    { id: "published", icon: "book-marked", label: "Published Recipes" }]
  },
  { section: "COMPLIANCE", items: [
    { id: "review-queue", icon: "clipboard-check", label: "Nutrition Review", count: 8 },
    { id: "nutrition-requests", icon: "messages-square", label: "Nutrition Requests" },
    { id: "verify-queue", icon: "microscope", label: "Verification Queue" }]
  },
  { section: "OPERATIONS", items: [
    { id: "calendar", icon: "calendar-days", label: "Publishing Calendar" },
    { id: "team-board", icon: "kanban-square", label: "Team Board", children: [
      { id: "create-assignment", icon: "plus", label: "Create Assignment" },
      { id: "my-assignments", icon: "user-check", label: "My Assignments" },
      { id: "assignments", icon: "kanban-square", label: "Team Board" }]
    }]
  },
  { section: "ADMINISTRATION", items: [
    { id: "settings", icon: "settings", label: "Settings" }]
  }],

  "admin": [
  { section: "DASHBOARD", items: [
    { id: "dashboard", icon: "layout-dashboard", label: "Dashboard" }]
  },
  { section: "PRODUCTS", items: [
    { id: "add-ingredient", icon: "leaf", label: "Add Ingredient", children: [
      { id: "add-ingredient", icon: "plus", label: "New Ingredient" },
      { id: "edit-ingredient", icon: "pencil", label: "Edit Ingredient" }]
    },
    { id: "create-recipe", icon: "upload-cloud", label: "Create New Recipe", children: [
      { id: "upload", icon: "plus", label: "New Recipe" },
      { id: "edit-recipe", icon: "pencil", label: "Edit Recipe" }]
    },
    { id: "library", icon: "book-open", label: "Library", children: [
      { id: "recipes", icon: "utensils-crossed", label: "Submitted Recipes" },
      { id: "ingredients", icon: "leaf", label: "Submitted Ingredients" },
      { id: "my-questions", icon: "message-circle-question", label: "My Questions" }]
    }]
  },
  { section: "COMPLIANCE", items: [
    { id: "review-queue", icon: "clipboard-check", label: "Nutrition Review", count: 4 },
    { id: "nutrition-requests", icon: "messages-square", label: "Nutrition Requests" },
    { id: "verify-queue", icon: "microscope", label: "Verification Queue" },
    { id: "audit", icon: "history", label: "Audit Log" }]
  },
  { section: "OPERATIONS", items: [
    { id: "calendar", icon: "calendar-days", label: "Publishing Calendar" },
    { id: "team-board", icon: "kanban-square", label: "Team Board", children: [
      { id: "create-assignment", icon: "plus", label: "Create Assignment" },
      { id: "my-assignments", icon: "user-check", label: "My Assignments" },
      { id: "assignments", icon: "kanban-square", label: "Team Board" }]
    },
    { id: "integrations", icon: "plug", label: "Integration", count: 7 },
    { id: "analytics", icon: "bar-chart-3", label: "Analytics", tag: "Beta" }]
  },
  { section: "ADMINISTRATION", items: [
    { id: "users", icon: "users", label: "People" },
    { id: "permissions", icon: "shield", label: "Roles & Permissions" },
    { id: "settings", icon: "settings", label: "Settings" }]
  }],

  "super-admin": [
  { section: "DASHBOARD", items: [
    { id: "dashboard", icon: "layout-dashboard", label: "Dashboard" }]
  },
  { section: "PRODUCTS", items: [
    { id: "add-ingredient", icon: "leaf", label: "Add Ingredient", children: [
      { id: "add-ingredient", icon: "plus", label: "New Ingredient" },
      { id: "edit-ingredient", icon: "pencil", label: "Edit Ingredient" }]
    },
    { id: "create-recipe", icon: "upload-cloud", label: "Create New Recipe", children: [
      { id: "upload", icon: "plus", label: "New Recipe" },
      { id: "edit-recipe", icon: "pencil", label: "Edit Recipe" }]
    },
    { id: "library", icon: "book-open", label: "Library", children: [
      { id: "recipes", icon: "utensils-crossed", label: "Submitted Recipes" },
      { id: "ingredients", icon: "leaf", label: "Submitted Ingredients" },
      { id: "my-questions", icon: "message-circle-question", label: "My Questions" }]
    }]
  },
  { section: "COMPLIANCE", items: [
    { id: "review-queue", icon: "clipboard-check", label: "Nutrition Review", count: 4 },
    { id: "nutrition-requests", icon: "messages-square", label: "Nutrition Requests" },
    { id: "verify-queue", icon: "microscope", label: "Verification Queue" },
    { id: "audit", icon: "history", label: "Audit Log" }]
  },
  { section: "OPERATIONS", items: [
    { id: "calendar", icon: "calendar-days", label: "Publishing Calendar" },
    { id: "team-board", icon: "kanban-square", label: "Team Board", children: [
      { id: "create-assignment", icon: "plus", label: "Create Assignment" },
      { id: "my-assignments", icon: "user-check", label: "My Assignments" },
      { id: "assignments", icon: "kanban-square", label: "Team Board" }]
    },
    { id: "integrations", icon: "plug", label: "Integration", count: 7 },
    { id: "analytics", icon: "bar-chart-3", label: "Analytics", tag: "Beta" }]
  },
  { section: "ADMINISTRATION", items: [
    { id: "users", icon: "users", label: "People" },
    { id: "permissions", icon: "shield", label: "Roles & Permissions" },
    { id: "settings", icon: "settings", label: "Settings" }]
  }]

};

// Reviewer is a server-backed role with a focused review surface. Keep this
// separate from Dietitian: their API grants and workflow eligibility differ.
NAV_BY_ROLE.reviewer = [
  { section: "DASHBOARD", items: [
    { id: "dashboard", icon: "layout-dashboard", label: "Dashboard" }
  ]},
  { section: "PRODUCTS", items: [
    { id: "library", icon: "book-open", label: "Library", children: [
      { id: "recipes", icon: "utensils-crossed", label: "Recipes" },
      { id: "ingredients", icon: "leaf", label: "Ingredients" }
    ]}
  ]},
  { section: "COMPLIANCE", items: [
    { id: "review-queue", icon: "clipboard-check", label: "Nutrition Review" }
  ]}
];

/* Compliance section, inserted after WORKSPACE for every role. Visible to all;
   edit actions inside the pages are gated to Admin / Super-admin. */
const COMPLIANCE_NAV = { section: "COMPLIANCE", items: [
  { id: "health-rules", icon: "heart-pulse", label: "Health Rules", children: [
    { id: "health-conditions", icon: "heart-pulse", label: "Health Conditions" },
    { id: "nutrient-rules", icon: "file-text", label: "Nutrient Rules" },
    { id: "ingredient-rules", icon: "leaf", label: "Ingredient Swap & Alternative Rules" },
    { id: "health-tag-rules", icon: "tag", label: "Health Tag Rules" }]
  },
  { id: "recipe-table", icon: "table-2", label: "Recipe Table" },
  { id: "allergen-table", icon: "triangle-alert", label: "Allergen Table" }]
};
function injectComplianceNav(nav) {
  if (nav.some((s) => s.section === "COMPLIANCE")) return nav;
  const wi = nav.findIndex((s) => s.section === "WORKSPACE");
  const out = nav.slice();
  out.splice(wi >= 0 ? wi + 1 : 1, 0, COMPLIANCE_NAV);
  return out;
}

/* Ensure every role can reach Help & Docs from the ADMINISTRATION group. */
function injectHelpNav(nav) {
  const out = nav.map((s) => ({ ...s, items: s.items.slice() }));
  let admin = out.find((s) => s.section === "ADMINISTRATION");
  if (!admin) { admin = { section: "ADMINISTRATION", items: [] }; out.push(admin); }
  if (!admin.items.some((it) => it.id === "help")) {
    admin.items.push({ id: "help", icon: "life-buoy", label: "Help & Docs" });
  }
  return out;
}

/* Loraa is a first-class workspace, not a demo-only modal. */
function injectLoraaNav(nav) {
  if (nav.some((s) => s.items.some((it) => it.id === "ask-laura"))) return nav;
  const out = nav.map((s) => ({ ...s, items: s.items.slice() }));
  const adminIndex = out.findIndex((s) => s.section === "ADMINISTRATION");
  out.splice(adminIndex >= 0 ? adminIndex : out.length, 0, {
    section: "LORAA AI",
    items: [{ id: "ask-laura", icon: "sparkles", label: "Loraa Command Center" }],
  });
  return out;
}

/* Digital Signage is a governed organization add-on. It is included with
   Enterprise and available for $15/month on Starter and Professional. */
function injectDigitalSignageNav(nav, role) {
  try {
    if (!window.DigitalSignage || !window.DigitalSignage.enabled()) return nav;
  } catch (e) { return nav; }
  if (typeof permAllowed === "function" && !permAllowed(role, "signage_view")) return nav;
  if (nav.some((s) => s.items.some((it) => it.id === "digital-signage"))) return nav;
  const out = nav.map((s) => ({ ...s, items: s.items.slice() }));
  const item = {
    id: "digital-signage",
    icon: "monitor-play",
    label: "Digital Signage",
    href: window.DigitalSignage.WORKSPACE_PATH || "/en/digital-signage",
    target: "_blank",
    tag: "Add-on",
  };
  const ai = out.findIndex((s) => s.section === "ADMINISTRATION");
  out.splice(ai >= 0 ? ai : out.length, 0, { section: "DIGITAL EXPERIENCE", items: [item] });
  return out;
}

/* Meal Programs, optional module, shown only when the subscription is enabled
   (Settings → Subscription & Features). Inserted right after WORKSPACE. */
const MEAL_PROGRAMS_NAV = { section: "MEAL PROGRAMS", items: [
  { id: "meal-programs", icon: "utensils", label: "Meal Programs", children: [
    { id: "mp-all",       icon: "layers",          label: "All Programs", filter: "all" },
    { id: "mp-draft",     icon: "file-pen-line",   label: "Drafts",       filter: "draft" },
    { id: "mp-review",    icon: "clipboard-check",  label: "In Review",    filter: "review" },
    { id: "mp-published", icon: "send",             label: "Published",    filter: "published" },
    { id: "mp-schedule",  icon: "calendar-days",    label: "Schedule",     page: "meal-planner" },
    { id: "mp-new",       icon: "plus",             label: "New Program",  page: "meal-program-builder" },
  ] }]
};
function injectMealProgramsNav(nav, role) {
  if (role !== "super-admin" && window.Entitlements && !window.Entitlements.has("meal_programs")) return nav;
  if (typeof mpEnabled !== "function" || !mpEnabled()) return nav;
  if (nav.some((s) => s.section === "MEAL PROGRAMS")) return nav;
  const wi = nav.findIndex((s) => s.section === "PRODUCTS");
  const out = nav.slice();
  out.splice(wi >= 0 ? wi + 1 : 1, 0, MEAL_PROGRAMS_NAV);
  return out;
}

/* Label Studio, Canadian NFt / label generation. Shown in a COMPLIANCE
   sidebar section when enabled (Settings → Subscription & Features). */
function injectLabelStudioNav(nav, role) {
  if (role !== "super-admin" && window.Entitlements && !window.Entitlements.has("label_studio")) return nav;
  if (typeof labelStudioEnabled !== "function" || !labelStudioEnabled()) return nav;
  if (role !== "admin" && role !== "super-admin") return nav;
  if (nav.some((s) => s.items.some((it) => it.id === "label-studio"))) return nav;
  const out = nav.slice();
  const qi = out.findIndex((s) => s.section === "COMPLIANCE");
  const item = { id: "label-studio", icon: "tag", label: "Label Studio", children: [
    { id: "label-studio", icon: "leaf", label: "CFIA Label" },
    { id: "fop-compliance", icon: "shield-check", label: "FOP Compliance" },
    { id: "fda-label", icon: "flag", label: "FDA Label" },
    { id: "supplements", icon: "pill", label: "Supplement Facts" },
    { id: "nafdac-label", icon: "globe", label: "NAFDAC Label" },
    { id: "intl-label:uk", icon: "globe", label: "UK Label" },
    { id: "intl-label:eu", icon: "globe", label: "EU Label" },
    { id: "intl-label:au", icon: "globe", label: "Australia Label" },
    { id: "intl-label:mx", icon: "globe", label: "Mexico Label" },
    { id: "nutrition-insights", icon: "lightbulb", label: "Nutrition Insights" },
  ] };
  if (qi >= 0) {
    out[qi] = { ...out[qi], items: [...out[qi].items, item] };
  } else {
    const wi = out.findIndex((s) => s.section === "PRODUCTS");
    out.splice(wi >= 0 ? wi + 1 : 1, 0, { section: "COMPLIANCE", items: [item] });
  }
  return out;
}

/* GS1 & Barcodes, organization-level module (Master PRD §5). Shown in the
   COMPLIANCE section when enabled (Settings → Subscription & Features). */
function injectGs1Nav(nav, role) {
  if (role !== "super-admin" && window.Entitlements && !window.Entitlements.has("gs1_barcodes")) return nav;
  if (typeof gs1Enabled !== "function" || !gs1Enabled()) return nav;
  if (nav.some((s) => s.items.some((it) => it.id === "gs1"))) return nav;
  const out = nav.map((s) => ({ ...s, items: s.items.slice() }));
  const item = { id: "gs1", icon: "scan-barcode", label: "GS1 & Barcodes" };
  const ci = out.findIndex((s) => s.section === "COMPLIANCE");
  if (ci >= 0) {
    // place just after Label Studio if present, else at end of COMPLIANCE
    const items = out[ci].items;
    const li = items.findIndex((it) => it.id === "label-studio");
    items.splice(li >= 0 ? li + 1 : items.length, 0, item);
  } else {
    const wi = out.findIndex((s) => s.section === "PRODUCTS");
    out.splice(wi >= 0 ? wi + 1 : 1, 0, { section: "COMPLIANCE", items: [item] });
  }
  return out;
}

/* Reports & Analytics, restaurant intelligence module (Reporting PRD).
   Own sidebar group; gated by feature flag + subscription tier. */
function injectCustomerCrmNav(nav, role) {
  try {
    if (role !== "super-admin" && window.Entitlements && !window.Entitlements.has("customer_experience")) return nav;
    if (!window.CustomerEngagement || !window.CustomerEngagement.available(role)) return nav;
    if (["admin", "super-admin", "manager"].indexOf(role) < 0) return nav;
    if (nav.some((s) => s.section === "CUSTOMER CRM")) return nav;
    return nav.concat([{ section: "CUSTOMER EXPERIENCE", items: [
      { id: "customer-portal", icon: "smartphone", label: "Customer Experience" },
    ] }]);
  } catch (e) { return nav; }
}
function injectReportsNav(nav, role) {
  if (role !== "super-admin" && window.Entitlements && !window.Entitlements.has("analytics_forecasting")) return nav;
  if (typeof rptEnabled !== "function" || !rptEnabled()) return nav;
  if (typeof restaurantPortalEnabled === "function" && !restaurantPortalEnabled()) return nav;
  if (nav.some((s) => s.section === "RESTAURANT PORTAL")) return nav;
  const out = nav.map((s) => ({ ...s, items: s.items.slice() }));
  const group = { section: "RESTAURANT PORTAL", items: [
    { id: "reports", icon: "layout-dashboard", label: "Dashboard" },
    { id: "reports:sales", icon: "dollar-sign", label: "Sales Intelligence" },
    { id: "reports:nutrition", icon: "salad", label: "Nutrition Intelligence" },
    { id: "reports:trends", icon: "users", label: "Customer Intelligence" },
    { id: "reports:menu", icon: "utensils-crossed", label: "Menu Intelligence" },
    { id: "reports:health", icon: "heart-pulse", label: "Health Intelligence" },
    { id: "reports:revenue", icon: "trending-up", label: "Revenue Intelligence" },
    { id: "reports:campaign", icon: "megaphone", label: "Campaign Intelligence" },
    { id: "reports:forecast", icon: "line-chart", label: "Forecasting" },
    { id: "reports:benchmark", icon: "gauge", label: "Benchmarking" },
    { id: "reports:ai", icon: "lightbulb", label: "AI Insights" },
    { id: "reports:exec", icon: "layout-dashboard", label: "Executive Dashboard" },
    { id: "reports:personalized", icon: "heart-handshake", label: "Customer Experience" },
    { id: "reports:exports", icon: "download", label: "Exports" },
  ] };
  // place just before ANALYTICS if present, else near the end
  const ai = out.findIndex((s) => s.section === "OPERATIONS");
  if (ai >= 0) out.splice(ai, 0, group); else out.push(group);
  return out;
}

/* Offerings, composes approved recipes into sellable units (PRD §9).
   Always available; sits in WORKSPACE just after the Library group. */
/* Inventory & Production, Enterprise module (PRD). Gated by ent("inventory").
   Sits in its own OPERATIONS-adjacent group just after PRODUCTS. */
function injectInventoryNav(nav, role) {
  try { if (role !== "super-admin" && window.Entitlements && !window.Entitlements.has("inventory")) return nav; } catch (e) {}
  if (nav.some((s) => s.items.some((it) => it.id === "inventory"))) return nav;
  const out = nav.map((s) => ({ ...s, items: s.items.slice() }));
  const group = { section: "INVENTORY & PRODUCTION", items: [
    { id: "inventory", icon: "package", label: "Inventory", children: [
      { id: "inventory", icon: "layout-dashboard", label: "Overview" },
      { id: "inv-items", icon: "boxes", label: "Items" },
      { id: "inv-lots", icon: "layers", label: "Lots" },
      { id: "inv-suppliers", icon: "truck", label: "Suppliers" },
      { id: "inv-po", icon: "clipboard-list", label: "Purchase Orders" },
      { id: "inv-pricelists", icon: "tag", label: "Price Lists" },
      { id: "inv-production", icon: "factory", label: "Production Runs" },
      { id: "inv-trace", icon: "git-branch", label: "Traceability" },
    ] },
  ] };
  const wi = out.findIndex((s) => s.section === "PRODUCTS");
  out.splice(wi >= 0 ? wi + 1 : 1, 0, group);
  return out;
}

function injectOfferingsNav(nav, role) {
  if (role !== "super-admin" && window.Entitlements && !window.Entitlements.has("meal_programs")) return nav;
  if (typeof offeringsComplianceEnabled === "function" && !offeringsComplianceEnabled()) return nav;
  if (nav.some((s) => s.items.some((it) => it.id === "offerings"))) return nav;
  const out = nav.map((s) => ({ ...s, items: s.items.slice() }));
  const wi = out.findIndex((s) => s.section === "PRODUCTS");
  const item = { id: "offerings", icon: "boxes", label: "Offerings", children: [
    { id: "of-product",  icon: "package",        label: "Products",          base: "offerings", filter: "product" },
    { id: "of-menu",     icon: "utensils",       label: "Menu Items",        base: "offerings", filter: "menu-item" },
    { id: "of-combo",    icon: "layers",         label: "Combo Meals",       base: "offerings", filter: "combo" },
    { id: "of-mealplan", icon: "calendar-range", label: "Meal Plans",        base: "offerings", filter: "meal-plan" },
    { id: "of-catering", icon: "users",          label: "Catering Packages", base: "offerings", filter: "catering" },
    { id: "restaurant-menus", icon: "book-open",  label: "Restaurant Menus" },
  ] };
  if (wi >= 0) {
    const items = out[wi].items;
    const li = items.findIndex((it) => it.id === "library");
    items.splice(li >= 0 ? li + 1 : items.length, 0, item);
    // Compliance Dashboard sits just after Offerings (reviewers+ only).
    items.splice(items.findIndex((it) => it.id === "offerings") + 1, 0, { id: "compliance-dashboard", icon: "shield-check", label: "Compliance" });
  } else {
    out.splice(1, 0, { section: "PRODUCTS", items: [item, { id: "compliance-dashboard", icon: "shield-check", label: "Compliance" }] });
  }
  return out;
}

function NavGroup({ item, page, setPage }) {
  const childTarget = (c) => c.page || c.base || (c.filter ? "meal-programs" : c.id);
  const childIds = item.children.map(childTarget);
  const isActive = childIds.includes(page) || (item.navTo && page === item.navTo);
  const [open, setOpen] = React.useState(false);
  const [fixedPos, setFixedPos] = React.useState(null);
  const btnRef = React.useRef(null);
  const isTopbar = () => !!(btnRef.current && btnRef.current.closest(".app.nav-topbar"));
  const toggle = () => {
    if (item.navTo) { setPage(item.navTo); return; }
    setOpen((o) => {
      const next = !o;
      if (next && btnRef.current) {
        const r = btnRef.current.getBoundingClientRect();
        if (isTopbar()) setFixedPos({ left: r.left, top: r.bottom + 4, side: false });
        else setFixedPos({ left: r.right + 8, top: r.top, side: true });
      } else setFixedPos(null);
      return next;
    });
  };
  React.useEffect(() => {
    if (!open || !fixedPos) return;
    const close = (e) => { if (btnRef.current && !btnRef.current.parentElement.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", close);
    return () => document.removeEventListener("mousedown", close);
  }, [open, fixedPos]);  return (
    <div className="nav-group">
      <button
        ref={btnRef}
        className={`nav-item ${isActive || (item.navTo && page === item.navTo) ? "active" : ""}`}
        onClick={toggle}
        title={item.label}>
        <Icon name={item.icon} size={18} className="nav-icon" />
        <span className="nav-label-text">{item.label}</span>
        {item.tag && <span className={`nav-tag nav-tag-${item.tag.toLowerCase()}`}>{item.tag}</span>}
        <Icon name={open ? "chevron-down" : "chevron-right"} size={14} className="nav-chevron" />
      </button>
      {open &&
      <div className={`nav-children ${fixedPos ? (fixedPos.side ? "nav-children-side" : "nav-children-fixed") : ""}`} style={fixedPos ? { position: "fixed", left: fixedPos.left, top: fixedPos.top } : undefined}>
          {item.children.map((c) => {
            const tgt = childTarget(c);
            const curFilter = c.base === "offerings" ? (window.__ofType || "all") : (window.__mpFilter || "all");
            const active = page === tgt && (c.filter ? curFilter === c.filter : true);
            const go = () => {
              if (c.filter && c.base === "offerings") { window.__ofType = c.filter; setPage("offerings"); window.dispatchEvent(new CustomEvent("of-filter", { detail: c.filter })); }
              else if (c.filter) { window.__mpFilter = c.filter; setPage("meal-programs"); window.dispatchEvent(new CustomEvent("mp-filter", { detail: c.filter })); }
              else setPage(tgt);
              setOpen(false);
            };
            return (
        <button
          key={c.id}
          className={`nav-item nav-child ${active ? "active" : ""}`}
          onClick={go}
          title={c.label}>
              <Icon name={c.icon} size={14} className="nav-icon" />
              <span className="nav-label-text">{c.label}</span>
            </button>
            );
        })}
        </div>
      }
    </div>);

}

function SidebarSection({ sec, page, setPage, renderItem }) {
  const key = "nutridms.navsec." + sec.section;
  const [open, setOpen] = React.useState(() => {
    try { const v = localStorage.getItem(key); return v === null ? true : v === "1"; } catch (e) { return true; }
  });
  const toggle = () => setOpen((o) => { const n = !o; try { localStorage.setItem(key, n ? "1" : "0"); } catch (e) {} return n; });
  return (
    <div className={`nav-section ${open ? "is-open" : "is-closed"}`} style={{ flex: "0 0 auto", paddingBottom: 4 }}>
      <button className="nav-label nav-label-btn" onClick={toggle} title={open ? "Collapse" : "Expand"}>
        <span>{sec.section}</span>
        <Icon name="chevron-down" size={13} className="nav-label-chev" />
      </button>
      <div className="nav-section-items">
        {sec.items.map(renderItem)}
      </div>
    </div>);
}

const RAIL_SHORT = {
  dashboard: "Home", "add-ingredient": "Add", "create-recipe": "Create",
  library: "Library", offerings: "Offers", "restaurant-menus": "Menus", "compliance-dashboard": "Comply",
  "label-studio": "Labels", gs1: "Barcode", "meal-programs": "Meals",
  calendar: "Plan", "team-board": "Team", "review-queue": "Review",
  "nutrition-requests": "Requests", users: "People", permissions: "Roles",
  integrations: "Connect", analytics: "Stats",
  settings: "Settings", help: "Help", reports: "Reports", "digital-signage": "Signage",
  more: "More", invite: "Invite", account: "Account"
};

const PAGE_PERMISSION_KEY = {
  "add-ingredient": "ing_create",
  "edit-ingredient": "edit_ingredient",
  "ingredients": "ing_view",
  "create-recipe": "create",
  "upload": "create",
  "edit-recipe": "edit_recipe",
  "recipes": "view_assigned",
  "review-queue": "nutrition_review",
  "ask-laura": "loraa_use",
  "loraa-analytics": "loraa_use",
  "offerings": "offerings_view",
  "label-studio": "label_view",
  "gs1": "gs1_view",
  "meal-programs": "mp_view",
  "meal-program-detail": "mp_view",
  "meal-program-builder": "mp_build",
  "compliance-dashboard": "rules_view",
  "digital-signage": "signage_view",
  "reports": "reports_view",
  "users": "users_view",
  "permissions": "roles_edit",
  "bulk-import": "csv_upload",
  "audit": "audit_view",
};
function pagePermissionKey(page) {
  if (typeof page === "string" && page.startsWith("reports")) return "reports_view";
  return PAGE_PERMISSION_KEY[page] || null;
}

const PAGE_ENTITLEMENT_KEY = {
  "add-ingredient": "recipe_ingredient_management",
  "edit-ingredient": "recipe_ingredient_management",
  "ingredient-detail": "recipe_ingredient_management",
  "ingredients": "master_ingredient_library",
  "library": "recipe_ingredient_management",
  "create-recipe": "recipes_subrecipes",
  "upload": "recipes_subrecipes",
  "edit-recipe": "recipes_subrecipes",
  "recipe-detail": "recipes_subrecipes",
  "recipes": "recipes_subrecipes",
  "review-feedback": "nutrition_review",
  "review-queue": "nutrition_review",
  "nutrition-requests": "loraa_qa",
  "verify-queue": "regulatory_validation",
  "calendar": "publishing_calendar",
  "team-board": "team_board",
  "assignments": "team_board",
  "create-assignment": "team_board",
  "my-assignments": "team_board",
  "integrations": "production_api",
  "analytics": "analytics_forecasting",
  "reports": "analytics_forecasting",
  "audit": "auditability",
  "bulk-import": "bulk_import",
  "permissions": "roles_access",
  "workspace": "multi_site",
  "offerings": "meal_programs",
  "restaurant-menus": "meal_programs",
  "meal-programs": "meal_programs",
  "meal-program-detail": "meal_programs",
  "meal-program-builder": "meal_programs",
  "meal-planner": "meal_programs",
  "label-studio": "label_studio",
  "fop-compliance": "fda_cfia_labels",
  "fda-label": "fda_cfia_labels",
  "nafdac-label": "fda_cfia_labels",
  "nutrition-insights": "regulatory_validation",
  "supplements": "supplements",
  "gs1": "gs1_barcodes",
  "compliance-dashboard": "compliance_view",
  "customer-portal": "customer_experience",
  "customer-crm": "customer_experience",
  "customer-qr": "customer_experience",
  "customer-dashboard": "customer_experience",
  "customer-mobile-view": "customer_experience",
  "digital-signage": "digital_signage",
  "health-conditions": "regulatory_validation",
  "health-tag-rules": "regulatory_validation",
  "nutrient-rules": "regulatory_validation",
  "ingredient-rules": "regulatory_validation",
  "recipe-table": "compliance_view",
  "allergen-table": "allergen_table"
};
function pageEntitlementKey(page) {
  if (typeof page !== "string") return null;
  if (page.startsWith("reports")) return "analytics_forecasting";
  if (page.startsWith("inv-") || page === "inventory") return "inventory";
  if (page.startsWith("intl-label:")) return "fda_cfia_labels";
  return PAGE_ENTITLEMENT_KEY[page] || null;
}
function pageEntitled(page, role) {
  const feature = pageEntitlementKey(page);
  if (!feature) return true;
  return !!(window.Entitlements && window.Entitlements.has(feature));
}

function Sidebar() {
  const { role, page, setPage, openCmd, lang } = useApp();
  // Re-render when admin toggles permissions or questions change so gated nav
  // items and live counts update without a reload.
  const [, bumpPerm] = React.useState(0);
  React.useEffect(() => {
    const h = () => bumpPerm(n => n + 1);
    window.addEventListener("nutridms-perms", h);
    window.addEventListener("nutridms-questions", h);
    window.addEventListener("nutridms-mealprograms", h);
    window.addEventListener("nutridms-labelstudio", h);
    window.addEventListener("nutridms-reports", h);
    window.addEventListener("nutridms-restaurant-portal", h);
    window.addEventListener("nutridms-gs1", h);
    window.addEventListener("nutridms-offerings-compliance", h);
    window.addEventListener("nutridms-cep", h);
    window.addEventListener("nutridms-digital-signage", h);
    window.addEventListener("nutridms-entitlements", h);
    window.addEventListener("storage", h); // cross-iframe writes (builder asks a question)
    return () => { window.removeEventListener("nutridms-perms", h); window.removeEventListener("nutridms-questions", h); window.removeEventListener("nutridms-mealprograms", h); window.removeEventListener("nutridms-labelstudio", h); window.removeEventListener("nutridms-reports", h); window.removeEventListener("nutridms-restaurant-portal", h); window.removeEventListener("nutridms-gs1", h); window.removeEventListener("nutridms-offerings-compliance", h); window.removeEventListener("nutridms-digital-signage", h); window.removeEventListener("nutridms-entitlements", h); window.removeEventListener("storage", h); };
  }, []);
  const allowChild = (c) => {
    const target = c.page || c.base || c.id;
    const permission = pagePermissionKey(target);
    return pageEntitled(target, role) && (!permission || permAllowed(role, permission));
  };
  // Live counts for the Question workflow (only count items that need attention).
  const me = currentUser(role);
  const liveCount = (id) => {
    try {
      if (id === "my-questions") return myQuestionsCount(me.initials);
      if (id === "nutrition-requests") return dietitianRequestsCount(me.initials);
    } catch (e) {}
    return undefined;
  };
  const withCount = (it) => {
    const clean = { ...it, count: undefined };
    const c = liveCount(it.id);
    return c === undefined ? clean : { ...clean, count: c || undefined };
  };
  const baseNav0 = NAV_BY_ROLE[role] || NAV_BY_ROLE["media-contributor"];
  // Compliance now lives under Settings (see SETTINGS_TABS), no longer a sidebar section.
  const baseNav = injectLoraaNav(
    injectDigitalSignageNav(
      injectInventoryNav(
        injectCustomerCrmNav(
          injectHelpNav(
            injectReportsNav(
              injectGs1Nav(
                injectOfferingsNav(
                  injectLabelStudioNav(injectMealProgramsNav(baseNav0, role), role),
                  role
                ),
                role
              ),
              role
            )
          ),
          role
        ),
        role
      ),
      role
    )
  );
  const nav = baseNav.map(sec => ({
    ...sec,
    items: sec.items
      .filter(it => pageEntitled(it.id, role) && (!pagePermissionKey(it.id) || permAllowed(role, pagePermissionKey(it.id))))
      .map(it => it.children ? { ...withCount(it), children: it.children.filter(allowChild).map(withCount) } : withCount(it)),
  })).filter(sec => sec.items.length);
  const roleMeta = ROLES[role];
  const user = currentUser(role);
  const [workspaceCount, setWorkspaceCount] = React.useState(() => enterpriseWorkspaceMemberships(
    window.__nutridmsMemberships || [], window.__nutridmsActiveOrganizationId || "",
  ).length);
  React.useEffect(() => {
    const refreshWorkspaces = () => setWorkspaceCount(enterpriseWorkspaceMemberships(
      window.__nutridmsMemberships || [], window.__nutridmsActiveOrganizationId || "",
    ).length);
    window.addEventListener("nutridms-user", refreshWorkspaces);
    window.addEventListener("nutridms-organization-switched", refreshWorkspaces);
    return () => { window.removeEventListener("nutridms-user", refreshWorkspaces); window.removeEventListener("nutridms-organization-switched", refreshWorkspaces); };
  }, []);
  const canSwitchWorkspace = role === "super-admin" && pageEntitled("workspace", role) && workspaceCount > 1;

  // Flatten section items into a single vertical rail. Items with children open a flyout.
  const railItems = [];
  nav.forEach((sec) => sec.items.forEach((it) => railItems.push(it)));

  const [flyout, setFlyout] = React.useState(null);
  const railRef = React.useRef(null);
  const hoverTimer = React.useRef(null);
  const [flyPos, setFlyPos] = React.useState({});
  React.useEffect(() => () => clearTimeout(hoverTimer.current), []);

  // Account menu (rail foot) — only admins/super-admins may invite.
  const canInvite = role === "admin" || role === "super-admin";
  const [acctMenu, setAcctMenu] = React.useState(false);
  const [acctPos, setAcctPos] = React.useState(null);
  React.useEffect(() => {
    if (!acctMenu) return;
    const onDoc = (e) => { if (!e.target.closest(".rail-acct-wrap")) setAcctMenu(false); };
    const onEsc = (e) => { if (e.key === "Escape") setAcctMenu(false); };
    document.addEventListener("mousedown", onDoc);
    document.addEventListener("keydown", onEsc);
    return () => { document.removeEventListener("mousedown", onDoc); document.removeEventListener("keydown", onEsc); };
  }, [acctMenu]);
  const toggleAcct = (e) => {
    const r = e.currentTarget.getBoundingClientRect();
    const railR = railRef.current ? railRef.current.getBoundingClientRect() : r;
    setAcctPos({ left: railR.right + 12, bottom: Math.max(8, window.innerHeight - r.bottom) });
    setAcctMenu((v) => !v);
  };
  const goProfile = () => { setAcctMenu(false); window.__settingsTab = "profile"; setPage("settings"); };
  const openFly = (id, cell) => {
    clearTimeout(hoverTimer.current);
    if (cell) {
      const r = cell.getBoundingClientRect();
      // Anchor to the rail's OUTER right edge so the flyout clears the scrollbar entirely.
      const railR = railRef.current ? railRef.current.getBoundingClientRect() : r;
      // Keep positions keyed per-item so a flyout that is fading out keeps its
      // last position instead of snapping to (0,0) and flashing.
      setFlyPos((p) => ({ ...p, [id]: { top: Math.max(8, r.top - 4), left: railR.right + 12 } }));
    }
    setFlyout(id);
  };
  const closeFly = () => { clearTimeout(hoverTimer.current); hoverTimer.current = setTimeout(() => setFlyout(null), 160); };

  const hasKids = (it) => it.children && it.children.length > 0;
  // Resolve a child nav entry to the page id it actually renders.
  const childTarget = (c) => c.page || c.base || (c.filter ? "meal-programs" : c.id);
  const goChild = (c) => {
    if (c.href) {
      if (c.target === "_blank") window.open(c.href, "_blank", "noopener,noreferrer");
      else window.location.href = c.href;
      return;
    }
    if (c.filter && c.base === "offerings") { window.__ofType = c.filter; setPage("offerings"); window.dispatchEvent(new CustomEvent("of-filter", { detail: c.filter })); }
    else if (c.filter) { window.__mpFilter = c.filter; setPage("meal-programs"); window.dispatchEvent(new CustomEvent("mp-filter", { detail: c.filter })); }
    else setPage(childTarget(c));
  };
  const isActive = (it) => page === it.id || (hasKids(it) && it.children.some((c) => page === childTarget(c)));
  // Every icon click routes somewhere. Team opens the board itself; the
  // assignment composer remains available from the flyout and board CTA.
  const go = (it) => {
    setFlyout(null);
    if (it.href) {
      if (it.target === "_blank") window.open(it.href, "_blank", "noopener,noreferrer");
      else window.location.href = it.href;
      return;
    }
    if (hasKids(it)) {
      const primary = it.id === "team-board"
        ? (it.children.find((child) => childTarget(child) === "assignments") || it.children[0])
        : it.children[0];
      goChild(primary);
      return;
    }
    setPage(it.id);
  };

  const renderBadges = (it) => (
    <React.Fragment>
      {it.count != null && <span className="rail-badge">{it.count > 99 ? "99+" : it.count}</span>}
      {it.count == null && it.tag && <span className="rail-dot" />}
    </React.Fragment>);

  const renderRailItem = (it) => {
    const cls = `rail-item ${isActive(it) ? "active" : ""} ${flyout === it.id ? "open" : ""}`;
    const inner = (
      <React.Fragment>
        <span className="rail-ic"><Icon name={it.icon} size={22} stroke={2.1} />{renderBadges(it)}</span>
        <span className="rail-label">{RAIL_SHORT[it.id] || it.label}</span>
      </React.Fragment>);
    return (
      <div key={it.id} className="rail-cell"
        onMouseEnter={hasKids(it) ? (e) => openFly(it.id, e.currentTarget) : () => closeFly()}
        onMouseLeave={hasKids(it) ? () => closeFly() : undefined}>
        {it.href && !hasKids(it) ?
          <a href={it.href} target={it.target || undefined} rel={it.target === "_blank" ? "noopener noreferrer" : undefined} className={cls} title={it.label}>{inner}</a> :
          <button className={cls} onClick={() => go(it)} title={it.label}>{inner}{hasKids(it) && <span className="rail-caret"><Icon name="chevron-right" size={12} /></span>}</button>}
        {hasKids(it) &&
          <div className={`rail-flyout ${flyout === it.id ? "is-open" : ""}`} aria-hidden={flyout !== it.id}
            style={flyPos[it.id] ? { top: flyPos[it.id].top, left: flyPos[it.id].left } : undefined}
            onMouseEnter={() => openFly(it.id)} onMouseLeave={() => closeFly()}>
            <div className="rail-flyout-h">{it.label}</div>
            {it.children.map((c) => {
              const tgt = childTarget(c);
              const curFilter = c.base === "offerings" ? (window.__ofType || "all") : (window.__mpFilter || "all");
              const active = page === tgt && (c.filter ? curFilter === c.filter : true);
              return (
                <button key={c.id} className={`rail-fly-item ${active ? "active" : ""}`} onClick={() => { setFlyout(null); goChild(c); }}>
                  <Icon name={c.icon || it.icon} size={16} /><span>{c.label}</span>{c.count != null && <span className="rail-fly-count">{c.count}</span>}
                </button>);
            })}
          </div>}
      </div>);
  };

  return (
    <aside ref={railRef} className="sidebar rail">
      <a className="rail-brand" href="#" onClick={(e) => { e.preventDefault(); setPage("dashboard"); }} title="NutriDMS">
        <BrandMark size={30} />
      </a>
      <nav className="rail-scroll">
        {railItems.map(renderRailItem)}
      </nav>
      <div className="rail-foot">
        {role === "super-admin" && pageEntitled("workspace", role) &&
          <button className="rail-item" onClick={() => window.dispatchEvent(new CustomEvent("nutridms-open-workspace"))} title="Workspaces">
            <span className="rail-ic"><Icon name="boxes" size={22} stroke={2.1} /></span>
            <span className="rail-label">Workspace</span>
          </button>}
        <button className="rail-item" onClick={openCmd} title="Search & jump to">
          <span className="rail-ic"><Icon name="layout-grid" size={22} stroke={2.1} /></span>
          <span className="rail-label">More</span>
        </button>
        {canInvite &&
          <button className="rail-item" onClick={() => { window.__settingsTab = "profile"; setPage("users"); }} title="Invite teammates">
            <span className="rail-ic"><Icon name="user-plus" size={22} stroke={2.1} /></span>
            <span className="rail-label">Invite</span>
          </button>}
        <div className="rail-acct-wrap">
          <button className={`rail-item rail-user ${acctMenu ? "open" : ""}`} title={`${user.name} · ${roleMeta.label}`} onClick={toggleAcct}>
            <UserAvatar tag="span" className="rail-avatar" initials={user.initials} />
            <span className="rail-label">Account</span>
          </button>
          {acctMenu && acctPos &&
            <div className="rail-acct-menu" style={{ position: "fixed", left: acctPos.left, bottom: acctPos.bottom, zIndex: 120 }}>
              <div className="rail-acct-head">
                <UserAvatar tag="span" className="rail-avatar" initials={user.initials} />
                <div className="rail-acct-meta">
                  <span className="rail-acct-name">{user.name}</span>
                  <small>{roleMeta.label}</small>
                </div>
              </div>
              {canSwitchWorkspace && <button className="rail-acct-item" onClick={() => {
                setAcctMenu(false);
                window.dispatchEvent(new CustomEvent("nutridms-open-account-switcher"));
              }}><Icon name="repeat-2" size={16} /><span>Switch workspace</span></button>}
              <button className="rail-acct-item" onClick={goProfile}><Icon name="user" size={16} /><span>My profile</span></button>
              <a className="rail-acct-item" href={`/${lang || "en"}/signin`} target="_top"><Icon name="log-out" size={16} /><span>Log out</span></a>
            </div>}
        </div>
      </div>
    </aside>);

}

function readProfileAvatar() {
  try {
    const authenticated = window.__nutridmsAuthenticatedUser || {};
    const hasAuthenticatedPhoto = Object.prototype.hasOwnProperty.call(authenticated, "profilePictureUrl")
      || Object.prototype.hasOwnProperty.call(authenticated, "profile_picture_url");
    const authenticatedPhoto = authenticated.profilePictureUrl || authenticated.profile_picture_url || "";
    const profile = window.NutriIdentityAssets && window.NutriIdentityAssets.profile
      ? window.NutriIdentityAssets.profile()
      : { photoDataUrl: "", color: "" };
    return { photo: hasAuthenticatedPhoto ? authenticatedPhoto : (profile.photoDataUrl || ""), color: profile.color || "" };
  }
  catch (e) { return { photo: "", color: "" }; }
}
function UserAvatar({ initials, className, tag }) {
  const Tag = tag || "div";
  const [pf, setPf] = React.useState(readProfileAvatar);
  React.useEffect(() => {
    const on = () => setPf(readProfileAvatar());
    window.addEventListener("nutridms-profile", on);
    window.addEventListener("storage", on);
    return () => { window.removeEventListener("nutridms-profile", on); window.removeEventListener("storage", on); };
  }, []);
  if (pf.photo) {
    return <Tag className={className} style={{ backgroundImage: `url("${pf.photo}")`, backgroundSize: "cover", backgroundPosition: "center", color: "transparent" }} aria-label="Profile photo"></Tag>;
  }
  return <Tag className={className} style={pf.color ? { background: pf.color } : undefined}>{initials}</Tag>;
}

function PersonAvatar({ person, initials, className, tag, style }) {
  const Tag = tag || "div";
  const source = person && typeof person === "object" ? person : {};
  const photo = String(source.profilePictureUrl || source.profile_picture_url || source.photoDataUrl || source.photo || source.avatar_url || "");
  const safePhoto = /^(?:data:image\/(?:png|jpeg|webp|svg\+xml);base64,|https?:\/\/|\/)/i.test(photo) ? photo : "";
  const label = initials || source.initials || "?";
  if (safePhoto) {
    return <Tag className={className} style={{ ...(style || {}), backgroundImage: `url("${safePhoto}")`, backgroundSize: "cover", backgroundPosition: "center", color: "transparent", overflow: "hidden" }} aria-label={(source.name || source.assignee_name || "Member") + " profile photo"}></Tag>;
  }
  return <Tag className={className} style={style}>{label}</Tag>;
}

function currentUser() {
  const registered = window.__nutridmsAuthenticatedUser;
  if (registered && registered.name) return registered;
  return { name: "NutriDMS User", initials: "NU", roleLabel: "Loading account…" };
}

function membershipOrganizationId(membership) {
  return String(membership && membership.organization && membership.organization.id || "");
}

function membershipOrganizationName(membership) {
  return String(membership && membership.organization && membership.organization.name || "Organization");
}

function membershipRoleKey(membership) {
  const value = membership && membership.role;
  return typeof value === "string"
    ? value
    : (value && (value.key || value.role_key)) ||
      (membership && (membership.member_role || membership.role_key)) || "";
}

function membershipRoleLabel(membership) {
  const value = membership && membership.role;
  const key = membershipRoleKey(membership);
  if (["super_admin", "superadmin"].includes(String(key).toLowerCase())) return "Super Admin";
  if (String(key).toLowerCase() === "admin") return "Admin";
  return String(value && (value.label || value.name) || key || "Member");
}

function enterpriseWorkspaceMemberships(memberships, activeId) {
  const rows = Array.isArray(memberships) ? memberships : [];
  const active = rows.find((membership) => membershipOrganizationId(membership) === String(activeId || "")) || rows[0];
  const activeOrg = active && active.organization || {};
  const billingOwner = String(activeOrg.billing_owner_id || activeOrg.id || "");
  if (!billingOwner) return [];
  return rows.filter((membership) => {
    const organization = membership && membership.organization || {};
    return String(organization.billing_owner_id || organization.id || "") === billingOwner;
  });
}

function activeMembershipFromSession(me) {
  const memberships = me && Array.isArray(me.memberships) ? me.memberships : [];
  const activeId = String(
    (me && me.active_organization_id) ||
    window.__nutridmsActiveOrganizationId ||
    (window.NutriAuth && window.NutriAuth.activeOrganization && window.NutriAuth.activeOrganization()) ||
    ""
  );
  return memberships.find((membership) => membershipOrganizationId(membership) === activeId) ||
    memberships[0] || null;
}

function normalizedDjangoUser(me) {
  const raw = me && me.user ? me.user : (me || {});
  const profile = raw.profile && typeof raw.profile === "object" ? raw.profile : {};
  const membership = activeMembershipFromSession(me);
  const explicitName = raw.name || raw.full_name || raw.display_name ||
    profile.name || profile.full_name || profile.display_name ||
    [raw.first_name, raw.last_name].filter(Boolean).join(" ");
  const email = String(raw.email || "").trim();
  const name = String(explicitName || raw.username || (email ? email.split("@")[0] : "")).trim();
  if (!name) return null;
  const parts = name.split(/\s+/).filter(Boolean);
  const membershipRole = membership && membership.role;
  const djangoRole = membershipRoleKey(membership);
  const organization = membership && membership.organization && typeof membership.organization === "object"
    ? membership.organization : {};
  return {
    id: raw.id || raw.uuid || email || name,
    membershipId: membership && membership.id,
    name,
    email,
    initials: (parts.length > 1 ? parts[0][0] + parts[parts.length - 1][0] : name.slice(0, 2)).toUpperCase(),
    djangoRole,
    roleLabel: membershipRoleLabel(membership),
    effectivePermissions: membership && Array.isArray(membership.effective_permissions) ? membership.effective_permissions : [],
    effectiveUiPermissions: membership && Array.isArray(membership.effective_ui_permissions) ? membership.effective_ui_permissions : [],
    uiRoleKey: membership && membership.ui_role_key,
    permissionOverrides: membership && membership.permission_overrides && typeof membership.permission_overrides === "object" ? membership.permission_overrides : {},
    rolePermissionMatrix: membership && membership.role_permission_matrix && typeof membership.role_permission_matrix === "object" ? membership.role_permission_matrix : {},
    accessRevision: Number(membership && membership.access_revision || 0),
    mfaRequired: !!(membership && membership.mfa_required),
    mfaEnabled: !!raw.mfa_enabled,
    profilePictureUrl: raw.profile_picture_url || profile.profile_picture_url || "",
    planEntitlements: membership && membership.plan_entitlements ? membership.plan_entitlements : null,
    organizationId: membershipOrganizationId(membership),
    organizationName: membershipOrganizationName(membership),
    workspaceBranding: organization.branding && typeof organization.branding === "object" ? organization.branding : {},
  };
}

function applyWorkspaceBranding(branding) {
  const source = branding && typeof branding === "object" ? branding : {};
  const logo = String(source.logo_url || "");
  const color = /^#[0-9a-f]{6}$/i.test(String(source.primary_color || "")) ? source.primary_color : "";
  try {
    if (logo) localStorage.setItem("nutridms_org_logo", logo);
    else localStorage.removeItem("nutridms_org_logo");
  } catch (e) { }
  if (color) {
    document.documentElement.style.setProperty("--brand-600", color);
    document.documentElement.style.setProperty("--workspace-brand", color);
  } else {
    document.documentElement.style.removeProperty("--workspace-brand");
  }
  if (source.brand_name) document.title = String(source.brand_name) + " · NutriDMS";
  window.__nutridmsWorkspaceBranding = source;
  window.dispatchEvent(new CustomEvent("nutridms-org", { detail: source }));
}

function applicationRoleFromDjango(value) {
  const key = String(value || "").trim().toLowerCase().replace(/-/g, "_");
  if (["super_admin", "superadmin", "super_administrator"].includes(key)) return "super-admin";
  if (["admin"].includes(key)) return "admin";
  if (["editorial_manager"].includes(key)) return "manager";
  if (["reviewer"].includes(key)) return "reviewer";
  if (["compliance_officer"].includes(key)) return "compliance";
  if (["media_contributor"].includes(key)) return "media-contributor";
  return "";
}

const LORAA_MESSENGER_DEFAULT = Object.freeze({ enabled: true, displaySeconds: 15 });

function normalizeLoraaMessengerPolicy(value) {
  const source = value && typeof value === "object" ? value : {};
  const seconds = Number(source.displaySeconds);
  const allowed = [0, 5, 10, 15, 30, 60];
  return {
    enabled: source.enabled !== false,
    displaySeconds: allowed.includes(seconds) ? seconds : LORAA_MESSENGER_DEFAULT.displaySeconds
  };
}

async function readLoraaMessengerPolicy(options) {
  if (window.NutriSettings && typeof window.NutriSettings.org === "function") {
    const settings = await window.NutriSettings.org(options);
    return normalizeLoraaMessengerPolicy(settings && settings.loraaMessenger);
  }
  return LORAA_MESSENGER_DEFAULT;
}

function loadNotificationInbox(force) {
  if (!window.NutriNotifications) return Promise.resolve([]);
  const refresh = window.NutriWorkspaceRefresh;
  const loader = () => window.NutriNotifications.list().then((payload) => {
    const rows = Array.isArray(payload) ? payload : ((payload && payload.results) || []);
    window.dispatchEvent(new CustomEvent("nutridms-notifications", { detail: rows }));
    return rows;
  });
  return refresh && typeof refresh.fetch === "function"
    ? refresh.fetch("notifications:inbox", loader, { scope: "org-user", ttlMs: refresh.DEFAULT_TTL_MS, force: force === true })
    : loader();
}

function loadLoraaMessengerInbox(force) {
  if (!window.NutriLoraa || typeof window.NutriLoraa.dueReminders !== "function") return loadNotificationInbox(force);
  const refresh = window.NutriWorkspaceRefresh;
  const due = () => window.NutriLoraa.dueReminders();
  const dueRequest = refresh && typeof refresh.fetch === "function"
    ? refresh.fetch("loraa:due-reminders", due, { scope: "org-user", ttlMs: refresh.DEFAULT_TTL_MS, force: force === true })
    : due();
  return dueRequest.then(() => loadNotificationInbox(force));
}

function LoraaMessenger({ onOpen, hidden }) {
  const [message, setMessage] = useState(null);
  const [policy, setPolicy] = useState(LORAA_MESSENGER_DEFAULT);
  const [policyReady, setPolicyReady] = useState(false);
  const policyRef = useRef(LORAA_MESSENGER_DEFAULT);
  const dismissed = useRef(new Set());

  const applyPolicy = (next) => {
    const normalized = normalizeLoraaMessengerPolicy(next);
    policyRef.current = normalized;
    setPolicy(normalized);
    if (!normalized.enabled) setMessage(null);
  };

  const dismiss = async (item) => {
    if (!item) return;
    dismissed.current.add(String(item.id || item.created_at || item.body));
    setMessage(null);
    if (item.id && window.NutriNotifications) {
      try { await window.NutriNotifications.markRead(item.id); } catch (_) {}
    }
  };

  useEffect(() => {
    let cancelled = false;
    const loadPolicy = async () => {
      if (!window.NutriData || !window.NutriData.isConnected || !window.NutriData.isConnected()) return;
      try {
        const next = await readLoraaMessengerPolicy();
        if (!cancelled) {
          applyPolicy(next);
          setPolicyReady(true);
        }
      } catch (_) {
        if (!cancelled) {
          applyPolicy(LORAA_MESSENGER_DEFAULT);
          setPolicyReady(true);
        }
      }
    };
    const receivePolicy = (event) => {
      const detail = event && event.detail;
      applyPolicy(detail && (detail.loraaMessenger || detail));
      setPolicyReady(true);
    };
    const reloadPolicy = () => {
      setPolicyReady(false);
      loadPolicy();
    };
    loadPolicy();
    window.addEventListener("nutridms-backend", loadPolicy);
    window.addEventListener("nutridms-organization-switched", reloadPolicy);
    window.addEventListener("nutridms-org-settings", receivePolicy);
    return () => {
      cancelled = true;
      window.removeEventListener("nutridms-backend", loadPolicy);
      window.removeEventListener("nutridms-organization-switched", reloadPolicy);
      window.removeEventListener("nutridms-org-settings", receivePolicy);
    };
  }, []);

  useEffect(() => {
    if (!policyReady) return undefined;
    let cancelled = false;
    const choose = (payload) => {
      if (!policyRef.current.enabled) {
        if (!cancelled) setMessage(null);
        return;
      }
      const rows = Array.isArray(payload) ? payload :
        (payload && Array.isArray(payload.results) ? payload.results : []);
      const next = rows.find((item) => item && item.kind === "loraa" && !item.read_at &&
        !dismissed.current.has(String(item.id || item.created_at || item.body)));
      if (!cancelled) setMessage(next || null);
    };
    const refresh = async () => {
      if (!policyRef.current.enabled || !window.NutriNotifications) return;
      try {
        choose(await loadLoraaMessengerInbox(false));
      } catch (_) {}
    };
    const receive = (event) => {
      if (!policyRef.current.enabled) return;
      const item = event && event.detail;
      if (!item || item.kind === "loraa") choose([{
        id: item && item.id,
        kind: "loraa",
        body: item && (item.body || item.message) || "Loraa has a reminder for you.",
        created_at: item && item.created_at || new Date().toISOString(),
        read_at: null
      }]);
    };
    const refreshCoordinator = window.NutriWorkspaceRefresh;
    if (refreshCoordinator && typeof refreshCoordinator.register === "function") {
      refreshCoordinator.register("loraa:messenger", () => loadLoraaMessengerInbox(true), {
        scope: "org-user",
        active: policyRef.current.enabled
      });
      refreshCoordinator.activate("loraa:messenger", policyRef.current.enabled);
    }
    refresh();
    const receiveInbox = (event) => choose(event && event.detail);
    window.addEventListener("nutridms-notification", receive);
    window.addEventListener("nutridms-loraa-nudge", receive);
    window.addEventListener("nutridms-notifications", receiveInbox);
    return () => {
      cancelled = true;
      if (refreshCoordinator && typeof refreshCoordinator.activate === "function") refreshCoordinator.activate("loraa:messenger", false);
      window.removeEventListener("nutridms-notification", receive);
      window.removeEventListener("nutridms-loraa-nudge", receive);
      window.removeEventListener("nutridms-notifications", receiveInbox);
    };
  }, [policyReady, policy.enabled]);

  useEffect(() => {
    if (!message || !policy.enabled || policy.displaySeconds === 0) return undefined;
    const timer = window.setTimeout(() => dismiss(message), policy.displaySeconds * 1000);
    return () => window.clearTimeout(timer);
  }, [message, policy.enabled, policy.displaySeconds]);

  // A due reminder must remain visible even when the full Loraa workspace is
  // open. Hiding it behind the agent made successful delivery look broken and
  // caused time-sensitive reminders to expire before users saw them.
  if (window.NUTRIDMS_PRODUCTION_CLEAN_SLATE || !message || !policy.enabled) return null;
  const sentAt = message.created_at ? new Date(message.created_at) : null;
  const timeLabel = sentAt && !Number.isNaN(sentAt.getTime())
    ? sentAt.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" })
    : "Now";
  return (
    <aside className="loraa-messenger" role="status" aria-live="polite">
      {policy.displaySeconds > 0 && (
        <span className="loraa-messenger-timer" style={{ animationDuration: `${policy.displaySeconds}s` }} aria-hidden="true" />
      )}
      <div className="loraa-messenger-head">
        <span className="loraa-messenger-logo"><img src="assets/loraa-logo.png" alt="" /></span>
        <span><strong>Loraa</strong><small>Reminder · {timeLabel}</small></span>
        <button type="button" className="loraa-messenger-close" onClick={() => dismiss(message)} aria-label="Dismiss Loraa reminder">×</button>
      </div>
      <p>{message.body}</p>
      <div className="loraa-messenger-actions">
        <button type="button" onClick={() => { dismiss(message); onOpen(); }}>Open Loraa</button>
        <button type="button" className="secondary" onClick={() => dismiss(message)}>Dismiss</button>
      </div>
    </aside>
  );
}

// ───────────────── Topbar ─────────────────
function Topbar() {
  const { role, setRole, lang, setLang, openCmd, openNotif, setMobileNav, setPage } = useApp();
  const [showLang, setShowLang] = useState(false);
  const [showRole, setShowRole] = useState(false);
  const [loraaOpen, setLoraaOpen] = useState(false);
  const [registeredUser, setRegisteredUser] = useState(() => window.__nutridmsAuthenticatedUser || null);
  const [accountMemberships, setAccountMemberships] = useState(() => window.__nutridmsMemberships || []);
  const [activeOrganizationId, setActiveOrganizationId] = useState(() => window.__nutridmsActiveOrganizationId || "");
  const [switchingOrganization, setSwitchingOrganization] = useState("");
  const [accountError, setAccountError] = useState("");
  const user = registeredUser || (window.NutriAuth ? { name: "Signed-in user", initials: "U" } : currentUser(role));
  const langMeta = LANGUAGES.find((l) => l.code === lang) || LANGUAGES[0];
  const workspaceMemberships = enterpriseWorkspaceMemberships(accountMemberships, activeOrganizationId);
  const canSwitchWorkspace = role === "super-admin" && pageEntitled("workspace", role) && workspaceMemberships.length > 1;

  useEffect(() => {
    const openLoraaCommandCenter = () => setPage("ask-laura");
    const openAccountSwitcher = () => {
      setShowLang(false);
      setShowRole(true);
    };
    window.__openLoraa = openLoraaCommandCenter;
    window.addEventListener("nutridms-loraa-open", openLoraaCommandCenter);
    window.addEventListener("nutridms-open-account-switcher", openAccountSwitcher);
    return () => {
      window.removeEventListener("nutridms-loraa-open", openLoraaCommandCenter);
      window.removeEventListener("nutridms-open-account-switcher", openAccountSwitcher);
      if (window.__openLoraa === openLoraaCommandCenter) delete window.__openLoraa;
    };
  }, []);

  useEffect(() => {
    let cancelled = false;
    const syncRegisteredUser = () => {
      const next = window.__nutridmsAuthenticatedUser || null;
      const memberships = Array.isArray(window.__nutridmsMemberships) ? window.__nutridmsMemberships : [];
      const activeId = String(window.__nutridmsActiveOrganizationId || (next && next.organizationId) || "");
      if (cancelled) return;
      setAccountMemberships(memberships);
      setActiveOrganizationId(activeId);
      setRegisteredUser(next);
      const mappedRole = applicationRoleFromDjango(next && (next.djangoRole || next.role));
      if (mappedRole && mappedRole !== role) setRole(mappedRole);
    };
    syncRegisteredUser();
    window.addEventListener("nutridms-user", syncRegisteredUser);
    window.addEventListener("nutridms-organization-switched", syncRegisteredUser);
    window.addEventListener("nutridms-backend", syncRegisteredUser);
    return () => {
      cancelled = true;
      window.removeEventListener("nutridms-user", syncRegisteredUser);
      window.removeEventListener("nutridms-organization-switched", syncRegisteredUser);
      window.removeEventListener("nutridms-backend", syncRegisteredUser);
    };
  }, []);

  const switchWorkspace = async (membership) => {
    if (!canSwitchWorkspace) {
      setAccountError("Workspace switching is available only on Enterprise.");
      return;
    }
    const organizationId = membershipOrganizationId(membership);
    if (!organizationId || organizationId === activeOrganizationId || switchingOrganization) {
      setShowRole(false);
      return;
    }
    setAccountError("");
    setSwitchingOrganization(organizationId);
    try {
      if (!window.NutriAuth || typeof window.NutriAuth.switchOrganization !== "function") {
        throw new Error("Secure account switching is not ready. Refresh and try again.");
      }
      await window.NutriAuth.switchOrganization(organizationId);
      window.top.location.reload();
    } catch (error) {
      setAccountError((error && error.message) || "Account switch failed.");
      setSwitchingOrganization("");
    }
  };

  const switchSignInAccount = async () => {
    setAccountError("");
    try {
      if (window.NutriAuth && typeof window.NutriAuth.logout === "function") {
        await window.NutriAuth.logout();
      }
    } finally {
      window.top.location.href = `/${lang || "en"}/signin?switch=1&return_to=%2F${lang || "en"}%2Fapp`;
    }
  };

  // Close popovers on outside click
  useEffect(() => {
    const onClick = (e) => {
      if (!e.target.closest(".popover-wrap")) {setShowLang(false);setShowRole(false);}
    };
    document.addEventListener("mousedown", onClick);
    return () => document.removeEventListener("mousedown", onClick);
  }, []);

  return (
    <div className="topbar">
      <button className="topbar-burger" onClick={() => setMobileNav && setMobileNav(true)} aria-label="Open navigation" title="Menu">
        <Icon name="menu" size={20} />
      </button>
      <button className="search" onClick={openCmd} style={{ cursor: "pointer" }}>
        <Icon name="search" size={18} />
        <span style={{ flex: 1, textAlign: "left" }}>Search recipes, contributors, tags…</span>
        <span className="kbd">⌘K</span>
      </button>

      <div className="topbar-right">
        <button className="loraa-globe" onClick={() => setPage("ask-laura")} aria-label="Open Loraa Command Center" title="Loraa Command Center">
          <img src="assets/loraa-logo.png" alt="Loraa" />
        </button>
        {typeof LoraaAsk === "function" && <LoraaAsk role={role} user={user} open={loraaOpen} onClose={() => setLoraaOpen(false)} />}
        <LoraaMessenger hidden={loraaOpen} onOpen={() => setLoraaOpen(true)} />
        {/* Language */}
        <div className="popover-wrap" style={{ position: "relative" }}>
          <button className="language-flag-button" title={`${langMeta.label} · Select language`} aria-label={`${langMeta.label}. Select language`} onClick={() => setShowLang((v) => !v)}>
            <span aria-hidden="true" style={{ fontSize: 18, lineHeight: 1 }}>{langMeta.flag}</span>
          </button>
          {showLang &&
          <div className="popover">
              <div className="popover-head">Display language</div>
              {LANGUAGES.map((l) =>
            <button key={l.code} className={`popover-item ${lang === l.code ? "on" : ""}`} onClick={() => {setLang(l.code);setShowLang(false);}}>
                  <span style={{ fontSize: 18 }}>{l.flag}</span>
                  <span>{l.label}</span>
                  <span className="muted" style={{ marginLeft: "auto", fontSize: 12 }}>{l.code.toUpperCase()}</span>
                  {lang === l.code && <Icon name="check" size={14} className="check" style={{ color: "var(--brand-700)" }} />}
                </button>
            )}
            </div>
          }
        </div>

        <button className="icon-btn" title="Help & Docs" onClick={() => setPage("help")}>
          <Icon name="circle-help" size={19} />
        </button>
        <button className="icon-btn" title="Notifications" onClick={openNotif}>
          <Icon name="bell" size={18} />
          {!window.NUTRIDMS_PRODUCTION_CLEAN_SLATE && <span className="dot" />}
        </button>

        <div className="popover-wrap" style={{ position: "relative" }}>
          <button className="role-pill" onClick={() => {
            setShowLang(false);
            setShowRole((value) => !value);
          }} title="Account and workspace">
            <UserAvatar className="avatar sm" initials={user.initials} />
            <div className="role-meta">
              <span>{user.name}</span>
              <small>{(registeredUser && registeredUser.organizationName) || (registeredUser && registeredUser.roleLabel) || ROLES[role].label}</small>
            </div>
            <Icon name="chevron-down" size={14} style={{ color: "var(--gray-500)" }} />
          </button>
          {showRole &&
          <div className="role-switch" style={{ width: 340 }}>
              <div className="role-switch-head">
                <span className="role-switch-t">{canSwitchWorkspace ? "Switch workspace" : "Account"}</span>
                <span className="role-switch-sub">{canSwitchWorkspace ? "Choose a workspace location" : "Manage your personal profile"}</span>
              </div>
              {canSwitchWorkspace && workspaceMemberships.map((membership) => {
                const organizationId = membershipOrganizationId(membership);
                const organizationName = membershipOrganizationName(membership);
                const roleKey = membershipRoleKey(membership);
                const mappedRole = applicationRoleFromDjango(roleKey);
                const meta = ROLE_SWITCH_META[mappedRole] || { icon: "building-2", accent: "green" };
                const active = organizationId === activeOrganizationId;
                return (
                  <button
                    key={membership.id || organizationId}
                    className={`role-switch-item acc-${meta.accent} ${active ? "on" : ""}`}
                    disabled={Boolean(switchingOrganization)}
                    onClick={() => switchWorkspace(membership)}>
                    <Icon name={meta.icon} size={17} stroke={2} />
                    <span className="role-switch-lbl" style={{ display: "grid", gap: 2 }}>
                      <span>{organizationName}</span>
                      <small style={{ color: "var(--gray-500)", fontWeight: 600 }}>{membershipRoleLabel(membership)}</small>
                    </span>
                    {switchingOrganization === organizationId
                      ? <Icon name="loader-circle" size={15} className="role-switch-check" />
                      : active && <Icon name="check" size={15} className="role-switch-check" />}
                  </button>
                );
              })}
              {accountError &&
                <div role="alert" style={{ margin: "6px 12px", padding: "9px 10px", borderRadius: 8, background: "#FEF3F2", color: "#B42318", fontSize: 12.5 }}>{accountError}</div>}
              <button className="role-switch-item" onClick={() => { setShowRole(false); window.__settingsTab = "profile"; setPage("settings"); }}>
                <Icon name="user" size={17} stroke={2} />
                <span className="role-switch-lbl">My profile</span>
                <Icon name="chevron-right" size={15} className="role-switch-check" />
              </button>
              <a className="role-switch-item" href={`/${lang || "en"}/signin`} target="_top">
                <Icon name="log-out" size={17} stroke={2} /><span className="role-switch-lbl">Log out</span>
              </a>
            </div>
          }
        </div>
      </div>
    </div>);

}

// ───────────────── Popover styles (injected once) ─────────────────
(function injectPopoverStyles() {
  if (document.getElementById("popover-styles")) return;
  const css = `
    .popover { position: absolute; top: calc(100% + 8px); right: 0; min-width: 200px; background: #fff; border: 1px solid var(--gray-200); border-radius: 12px; box-shadow: 0 12px 32px rgba(0,0,0,.12); padding: 6px; z-index: 50; }
    .popover-head { font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--gray-500); padding: 8px 10px 4px; font-weight: 700; }
    .popover-item { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; padding: 8px 10px; border-radius: 8px; font-size: 14px; font-weight: 500; color: var(--text-primary); transition: background .1s ease; }
    .popover-item:hover { background: var(--gray-50); }
    .popover-item.on { background: var(--green-50); color: var(--green-800); }
    .app[data-theme="dark"] .popover { background: #161B22; border-color: #1F242F; }
    .app[data-theme="dark"] .popover-item { color: #E7ECEA; }
    .app[data-theme="dark"] .popover-item:hover { background: #1F242F; }
  `;
  const s = document.createElement("style");s.id = "popover-styles";s.textContent = css;document.head.appendChild(s);
})();

// ───────────────── Breadcrumbs ─────────────────
function Crumbs({ path }) {
  const { setPage } = useApp();
  return (
    <div className="crumbs">
      <button className="icon-btn" style={{ width: 24, height: 24, color: "var(--gray-500)" }} onClick={() => setPage("dashboard")}>
        <Icon name="home" size={14} />
      </button>
      {path.map((p, i) =>
      <React.Fragment key={i}>
          <Icon name="chevron-right" size={12} className="sep" />
          {i === path.length - 1 ?
        <span className="current">{p.label}</span> :
        <button onClick={() => p.onClick && p.onClick()}>{p.label}</button>}
        </React.Fragment>
      )}
    </div>);

}

// ───────────────── Stat ─────────────────
function Stat({ label, value, icon, tone = "brand", trend, sub, onClick }) {
  return (
    <div className={`stat ${onClick ? "clickable" : ""}`} onClick={onClick}
      role={onClick ? "button" : undefined} tabIndex={onClick ? 0 : undefined}
      onKeyDown={onClick ? (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onClick(); } } : undefined}>
      <div className="stat-head">
        <div className={`stat-icon ${tone}`}><Icon name={icon} size={22} /></div>
        {trend &&
        <div className={`stat-trend ${trend.direction}`}>
            {trend.direction === "up" && <Icon name="trending-up" size={14} stroke={2.5} />}
            {trend.direction === "down" && <Icon name="trending-down" size={14} stroke={2.5} />}
            {trend.direction === "flat" && <Icon name="minus" size={14} stroke={2.5} />}
            <span>{trend.value}</span>
            <span className="stat-sub">{trend.label}</span>
          </div>
        }
      </div>
      <div className="stat-label">{label}</div>
      <div className="stat-value">{value}</div>
      {sub && <div className="muted" style={{ fontSize: 12, marginTop: 4 }}>{sub}</div>}
      {onClick && <div className="stat-cta"><span>View</span><Icon name="arrow-right" size={13} stroke={2.4} /></div>}
    </div>);

}

// ───────────────── View toggle ─────────────────
function ViewToggle({ value, onChange }) {
  return (
    <div className="view-toggle">
      <button className={value === "grid" ? "on" : ""} onClick={() => onChange("grid")} title="Grid"><Icon name="layout-grid" size={16} /></button>
      <button className={value === "list" ? "on" : ""} onClick={() => onChange("list")} title="List"><Icon name="list" size={16} /></button>
    </div>);

}

// ───────────────── Recipe card ─────────────────
function RecipeCard({ recipe, onOpen }) {
  return (
    <div className="recipe-card" onClick={() => onOpen && onOpen(recipe)}>
      <div className="recipe-cover" style={{ backgroundImage: `url("${recipe.cover}")` }}>
        <div className="corner-pill"><StatusPill status={recipe.status} item={recipe} kind="recipe" /></div>
        {recipe.priority === "high" && <div className="corner-pill-right"><PriorityPill priority={recipe.priority} /></div>}
      </div>
      <div className="recipe-body">
        <h4 className="recipe-title">{recipe.name}</h4>
        <div className="recipe-meta">
          <span><Icon name="clock" size={14} /> {recipe.duration} min</span>
          <span><Icon name="users" size={14} /> {recipe.servings}</span>
          <span><Icon name="flame" size={14} /> {recipe.calories} kcal</span>
        </div>
        <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
          <span className="tag">{recipe.cuisine}</span>
          <span className="tag" style={{ background: "var(--green-50)", color: "var(--green-700)" }}>{recipe.category}</span>
        </div>
        <div style={{ marginTop: "auto", display: "flex", alignItems: "center", justifyContent: "space-between", gap: 10, paddingTop: 8 }}>
          <div className="row" style={{ gap: 8 }}>
            <PersonAvatar person={recipe.contributor} className="avatar sm" style={{ background: "var(--gray-100)", color: "var(--gray-700)" }} />
            <span style={{ fontSize: 12, color: "var(--gray-600)", fontWeight: 500 }}>{recipe.contributor.name}</span>
          </div>
          <Icon name="arrow-up-right" size={16} style={{ color: "var(--gray-500)" }} />
        </div>
      </div>
    </div>);

}

// ───────────────── Modal ─────────────────
function Modal({ open, onClose, title, subtitle, children, footer, width = 560 }) {
  useEffect(() => {
    if (!open) return;
    const onKey = (e) => {if (e.key === "Escape") onClose && onClose();};
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, [open, onClose]);
  if (!open) return null;
  return (
    <div className="modal-bg" onMouseDown={(e) => {if (e.target === e.currentTarget) onClose && onClose();}}>
      <div className="modal" style={{ maxWidth: width }}>
        <div className="modal-head">
          <div>
            <h3 style={{ margin: 0, fontFamily: "var(--serif)", fontSize: 24, letterSpacing: "-.01em" }}>{title}</h3>
            {subtitle && <p style={{ margin: "4px 0 0", color: "var(--gray-600)", fontSize: 14 }}>{subtitle}</p>}
          </div>
          <button className="icon-btn" onClick={onClose}><Icon name="x" size={18} /></button>
        </div>
        <div className="modal-body">{children}</div>
        {footer && <div className="modal-foot">{footer}</div>}
      </div>
    </div>);

}

// ───────────────── Report Bar (period nav · PDF · email schedule) ─────────────────
function ReportBar({ feature = "Report", role }) {
  const [period, setPeriod] = useState("weekly");
  const [emailOpen, setEmailOpen] = useState(false);
  const [freq, setFreq] = useState("weekly");
  const [email, setEmail] = useState(() => { try { return (currentUser && currentUser(role) && currentUser(role).email) || ""; } catch (e) { return ""; } });
  const [saved, setSaved] = useState(false);
  const PERIODS = [["daily", "Daily"], ["weekly", "Weekly"], ["monthly", "Monthly"], ["yearly", "Yearly"]];
  const SCHED_KEY = "nutridms_report_sched_v1";

  const downloadPdf = () => {
    document.body.setAttribute("data-report-print", feature + " · " + period);
    window.print();
    setTimeout(() => document.body.removeAttribute("data-report-print"), 500);
  };
  const saveSchedule = () => {
    try {
      const all = JSON.parse(localStorage.getItem(SCHED_KEY) || "{}");
      all[feature] = { freq, email, at: Date.now() };
      localStorage.setItem(SCHED_KEY, JSON.stringify(all));
    } catch (e) {}
    setSaved(true);
    if (window.__toast) window.__toast(feature + " report scheduled · " + freq + " → " + email);
    setTimeout(() => { setEmailOpen(false); setSaved(false); }, 900);
  };

  return (
    <div className="rpt-bar">
      <div className="rpt-seg" role="tablist" aria-label="Report period">
        {PERIODS.map(([id, l]) => (
          <button key={id} role="tab" aria-selected={period === id} className={period === id ? "on" : ""} onClick={() => setPeriod(id)}>{l}</button>
        ))}
      </div>
      <div className="rpt-actions">
        <button className="btn ghost sm" onClick={() => setEmailOpen(true)}><Icon name="mail" size={14} stroke={2.2} /> Email reports</button>
        <button className="btn secondary sm" onClick={downloadPdf}><Icon name="file-down" size={14} stroke={2.2} /> Download PDF</button>
      </div>
      {emailOpen && (
        <div className="rpt-drawer-scrim" onMouseDown={(e) => e.target === e.currentTarget && setEmailOpen(false)}>
          <div className="rpt-drawer" role="dialog" aria-label="Email report schedule">
            <div className="rpt-drawer-head">
              <div><h3>Automated {feature} report</h3><p>NutriDMS emails a PDF of this report on your schedule.</p></div>
              <button className="icon-btn" onClick={() => setEmailOpen(false)}><Icon name="x" size={18} /></button>
            </div>
            <div className="rpt-drawer-body">
              <label className="rpt-lbl">Frequency</label>
              <div className="rpt-seg rpt-seg-full">
                {PERIODS.map(([id, l]) => <button key={id} className={freq === id ? "on" : ""} onClick={() => setFreq(id)}>{l}</button>)}
              </div>
              <label className="rpt-lbl" style={{ marginTop: 18 }}>Send to</label>
              <input className="rpt-input" type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="name@company.com" />
              <div className="rpt-note"><Icon name="info" size={12} /> Recipients only receive data they have permission to see. Manage all schedules in Settings → Notifications.</div>
            </div>
            <div className="rpt-drawer-foot">
              <button className="btn secondary" onClick={() => setEmailOpen(false)}>Cancel</button>
              <button className="btn primary" disabled={!email || saved} onClick={saveSchedule}>{saved ? <><Icon name="check" size={15} /> Scheduled</> : <><Icon name="mail" size={15} /> Schedule {freq}</>}</button>
            </div>
          </div>
        </div>
      )}
    </div>);

}
if (typeof window !== "undefined") window.ReportBar = ReportBar;

// ───────────────── Toast ─────────────────
function Toast({ msg, kind = "success", onDone }) {
  const [leaving, setLeaving] = useState(false);
  useEffect(() => {
    if (!msg) return;
    setLeaving(false);
    const hide = setTimeout(() => setLeaving(true), 3200);
    const done = setTimeout(() => onDone(), 3600);
    return () => { clearTimeout(hide); clearTimeout(done); };
  }, [msg, onDone]);
  if (!msg) return null;
  // Detect the semantic from the message so toggles read as ON / OFF / saved.
  const text = typeof msg === "string" ? msg : (msg && msg.text) || "";
  const low = text.toLowerCase();
  const isOff = /\b(disabled|turned off|hidden|removed|deleted|revoked|off)\b/.test(low);
  const isOn = /\b(enabled|turned on|added|shown|activated|on)\b/.test(low);
  const state = isOff ? "off" : isOn ? "on" : (kind === "info" ? "info" : "saved");
  const ic = { on: "check-circle-2", off: "minus-circle", saved: "check-circle-2", info: "info" }[state];
  const label = { on: "Enabled", off: "Disabled", saved: "Done", info: "Notice" }[state];
  return (
    <div className={`nds-toast nds-toast-${state}${leaving ? " leaving" : ""}`} role="status" aria-live="polite">
      <span className="nds-toast-orb">
        <svg viewBox="0 0 24 24" width="17" height="17" aria-hidden="true"><path d="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10Z" fill="currentColor" opacity=".22"/><path d="M2 21c0-3 1.85-5.36 5.08-6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" fill="none"/></svg>
        <i className="nds-toast-badge"><Icon name={ic} size={12} stroke={2.6} /></i>
      </span>
      <div className="nds-toast-body">
        <span className="nds-toast-label">{label}</span>
        <span className="nds-toast-msg">{text}</span>
      </div>
      <button className="nds-toast-x" onClick={() => { setLeaving(true); setTimeout(onDone, 240); }} aria-label="Dismiss"><Icon name="x" size={14} stroke={2.4} /></button>
      <span className="nds-toast-timer" />
    </div>);

}

// ───────────────── Cmd palette ─────────────────
/* ── Global search index helpers ── */
const CMD_G = (name) => { try { return eval(name); } catch (e) { return (typeof window !== "undefined" && window[name]) || undefined; } };
// Curated quick actions (shown on open + always searchable). Filtered to the role's real pages below.
const CMD_ACTIONS = [
  { id: "dashboard",     label: "Go to Dashboard",           icon: "layout-dashboard" },
  { id: "recipes",       label: "Go to Recipes",             icon: "utensils-crossed" },
  { id: "ingredients",   label: "Go to Ingredients",         icon: "leaf" },
  { id: "upload",        label: "Create new recipe",         icon: "upload-cloud" },
  { id: "add-ingredient",label: "Add new ingredient",        icon: "leaf" },
  { id: "review-queue",  label: "Open Review Queue",         icon: "clipboard-check" },
  { id: "calendar",      label: "Open Publishing Calendar",  icon: "calendar-days" },
  { id: "assignments",   label: "Open Team Board",           icon: "kanban-square" },
  { id: "reports",       label: "Open Reports",              icon: "bar-chart-3" },
  { id: "users",         label: "Open People",               icon: "users" },
  { id: "permissions",   label: "Open Roles & Permissions",  icon: "shield" },
  { id: "audit",         label: "Open Audit Log",            icon: "scroll-text" },
  { id: "settings",      label: "Open Settings",             icon: "settings" },
  { id: "help",          label: "Open Help & Docs",          icon: "life-buoy" },
];
// Flatten a role's nav into a deduped {id,label,section,icon} page list.
function cmdPagesForRole(role) {
  const nav = (typeof NAV_BY_ROLE !== "undefined" ? NAV_BY_ROLE : {})[role] || [];
  const out = [];
  const seen = new Set();
  const push = (it, section) => {
    if (!it || !it.id || seen.has(it.id)) return;
    seen.add(it.id);
    out.push({ id: it.id, label: it.label, section, icon: it.icon || "square" });
  };
  nav.forEach((sec) => (sec.items || []).forEach((it) => {
    push(it, sec.section);
    (it.children || []).forEach((c) => push(c, sec.section));
  }));
  return out;
}

function CmdPalette({ open, onClose }) {
  const { role, setPage } = useApp();
  const [q, setQ] = useState("");
  const [active, setActive] = useState(0);
  const scrollRef = useRef(null);
  useEffect(() => { if (open) { setQ(""); setActive(0); } }, [open]);

  const ql = q.trim().toLowerCase();
  const rank = (s) => { if (!s) return 999; const i = s.toLowerCase().indexOf(ql); return i < 0 ? 999 : i; };
  const hit = (s) => s && s.toLowerCase().includes(ql);

  // Build role-scoped, deduped page set so actions never point at pages the role can't see.
  const pages = useMemo(() => cmdPagesForRole(role), [role]);
  const validIds = useMemo(() => new Set(pages.map((p) => p.id)), [pages]);

  const groups = useMemo(() => {
    if (!open) return [];
    const RECIPES_D = CMD_G("RECIPES") || CMD_G("window").RECIPES || [];
    const INGS = CMD_G("INGREDIENT_ITEMS") || (typeof window !== "undefined" && window.INGREDIENT_ITEMS) || [];
    const USERS_D = CMD_G("USERS") || [];
    const ROLES_D = CMD_G("ROLES") || (typeof window !== "undefined" && window.ROLES) || {};
    const HELP = (typeof window !== "undefined" && window.HELP_DOCS) || [];
    const helpCan = (typeof window !== "undefined" && window.helpRoleCan) || (() => true);

    // Actions, restricted to pages this role actually has
    const actions = CMD_ACTIONS
      .filter((a) => validIds.has(a.id))
      .filter((a) => !ql || hit(a.label))
      .sort((a, b) => rank(a.label) - rank(b.label));

    // Pages (only surfaced while searching, to keep the open state calm)
    const pageHits = !ql ? [] : pages
      .filter((p) => hit(p.label) || hit(p.section))
      .filter((p) => !CMD_ACTIONS.some((a) => a.id === p.id && hit(a.label))) // avoid dupe with actions
      .sort((a, b) => rank(a.label) - rank(b.label))
      .slice(0, 8);

    const recipes = RECIPES_D
      .filter((r) => !ql || hit(r.name) || hit(r.cuisine) || hit(r.category))
      .sort((a, b) => rank(a.name) - rank(b.name))
      .slice(0, ql ? 6 : 4);

    const ings = !ql ? [] : INGS
      .filter((i) => hit(i.name) || hit(i.canonical) || hit(i.category))
      .sort((a, b) => rank(a.name) - rank(b.name))
      .slice(0, 6);

    // People only when searching (and only for roles that can open user management)
    const people = (!ql || !validIds.has("users")) ? [] : USERS_D
      .filter((u) => hit(u.name) || hit(u.email) || hit(u.team) || hit((ROLES_D[u.role] || {}).label))
      .sort((a, b) => rank(a.name) - rank(b.name))
      .slice(0, 5);

    const helpDocs = !ql ? [] : HELP
      .filter((d) => helpCan(d, role))
      .filter((d) => hit(d.title) || hit(d.summary) || hit(d.terms))
      .sort((a, b) => rank(a.title) - rank(b.title))
      .slice(0, 5);

    const g = [];
    if (actions.length) g.push({ key: "actions", label: ql ? "Actions" : "Quick actions", items: actions.map((a) => ({
      key: "a-" + a.id, icon: a.icon, title: a.label, arrow: true,
      run: () => setPage(a.id),
    })) });
    if (pageHits.length) g.push({ key: "pages", label: "Pages", items: pageHits.map((p) => ({
      key: "p-" + p.id, icon: p.icon, title: p.label, meta: p.section, arrow: true,
      run: () => setPage(p.id),
    })) });
    if (recipes.length) g.push({ key: "recipes", label: "Recipes", items: recipes.map((r) => ({
      key: "r-" + r.id, thumb: r.cover, title: r.name, sub: `${r.cuisine} · ${r.category}`, status: r.status,
      run: () => (window.__openRecipe ? window.__openRecipe(r) : setPage("recipes")),
    })) });
    if (ings.length) g.push({ key: "ings", label: "Ingredients", items: ings.map((i) => ({
      key: "i-" + i.id, icon: "leaf", title: i.name, sub: `${i.canonical || ""}${i.category ? " · " + i.category : ""}`, status: i.status,
      run: () => (window.__openIngredient ? window.__openIngredient(i) : setPage("ingredients")),
    })) });
    if (people.length) g.push({ key: "people", label: "People", items: people.map((u) => ({
      key: "u-" + u.id, initials: u.initials, title: u.name, sub: `${(ROLES_D[u.role] || {}).label || u.role}${u.team ? " · " + u.team : ""}`,
      run: () => { window.__cmdUser = u.id; setPage("users"); },
    })) });
    if (helpDocs.length) g.push({ key: "help", label: "Help & Docs", items: helpDocs.map((d) => ({
      key: "h-" + d.id, icon: "life-buoy", title: d.title, sub: d.summary, arrow: true,
      run: () => { window.__helpOpenDoc = d.id; setPage("help"); },
    })) });
    return g;
  }, [open, ql, role, pages, validIds]);

  // Flatten to a single list for keyboard navigation
  const flat = useMemo(() => groups.flatMap((g) => g.items), [groups]);
  useEffect(() => { setActive(0); }, [ql]);
  useEffect(() => {
    const el = scrollRef.current && scrollRef.current.querySelector(`[data-cmdidx="${active}"]`);
    if (el && scrollRef.current) {
      const c = scrollRef.current, top = el.offsetTop, bot = top + el.offsetHeight;
      if (top < c.scrollTop) c.scrollTop = top - 8;
      else if (bot > c.scrollTop + c.clientHeight) c.scrollTop = bot - c.clientHeight + 8;
    }
  }, [active]);

  if (!open) return null;

  const runItem = (it) => { if (it && it.run) it.run(); onClose(); };
  const onKey = (e) => {
    if (e.key === "ArrowDown") { e.preventDefault(); setActive((a) => flat.length ? (a + 1) % flat.length : 0); }
    else if (e.key === "ArrowUp") { e.preventDefault(); setActive((a) => flat.length ? (a - 1 + flat.length) % flat.length : 0); }
    else if (e.key === "Enter") { e.preventDefault(); if (flat[active]) runItem(flat[active]); }
    else if (e.key === "Escape") { e.preventDefault(); onClose(); }
  };

  let idx = -1;
  return (
    <div className="modal-bg" onMouseDown={(e) => {if (e.target === e.currentTarget) onClose();}} style={{ alignItems: "flex-start", paddingTop: "12vh" }}>
      <div className="modal cmd-modal" style={{ maxWidth: 640, width: "100%" }} onKeyDown={onKey}>
        <div style={{ display: "flex", alignItems: "center", gap: 10, padding: "16px 18px", borderBottom: "1px solid var(--gray-100)" }}>
          <Icon name="search" size={18} style={{ color: "var(--gray-500)" }} />
          <input autoFocus value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search recipes, ingredients, people, pages…" style={{ flex: 1, border: 0, outline: 0, fontSize: 15, background: "transparent", fontFamily: "inherit", color: "var(--text-primary)" }} />
          <span className="kbd">esc</span>
        </div>
        <div ref={scrollRef} style={{ maxHeight: 460, overflowY: "auto", padding: "8px 0" }}>
          {flat.length === 0 &&
            <div style={{ padding: "40px 18px", textAlign: "center", color: "var(--gray-500)" }}>
              <Icon name="search-x" size={26} style={{ color: "var(--gray-400)", marginBottom: 8 }} />
              <div style={{ fontSize: 14, fontWeight: 600, color: "var(--gray-600)" }}>No results for “{q}”</div>
              <div style={{ fontSize: 12.5, marginTop: 4 }}>Try a recipe name, an ingredient, a teammate, or a page.</div>
            </div>
          }
          {groups.map((g) =>
            <div key={g.key}>
              <div style={{ padding: "10px 18px 4px", fontSize: 11, color: "var(--gray-500)", fontWeight: 700, letterSpacing: ".08em", textTransform: "uppercase" }}>{g.label}</div>
              {g.items.map((it) => {
                idx++;
                const on = idx === active;
                const myIdx = idx;
                return (
                  <button key={it.key} data-cmdidx={myIdx} onMouseMove={() => setActive(myIdx)} onClick={() => runItem(it)}
                    className={`cmd-item${on ? " active" : ""}`} style={cmdItem}>
                    {it.thumb ? <div className="thumb sm" style={{ backgroundImage: `url("${it.thumb}")`, flexShrink: 0 }} />
                      : it.initials ? <span className="cmd-avatar">{it.initials}</span>
                      : <span className="cmd-ic"><Icon name={it.icon} size={16} /></span>}
                    <div style={{ display: "flex", flexDirection: "column", alignItems: "flex-start", minWidth: 0, flex: 1 }}>
                      <span style={{ fontWeight: 600, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", maxWidth: "100%" }}>{it.title}</span>
                      {it.sub && <span style={{ fontSize: 12, color: "var(--gray-500)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", maxWidth: "100%" }}>{it.sub}</span>}
                    </div>
                    {it.meta && <span style={{ fontSize: 10.5, fontWeight: 700, letterSpacing: ".04em", color: "var(--gray-400)", textTransform: "uppercase", flexShrink: 0 }}>{it.meta}</span>}
                    {it.status && <StatusPill status={it.status} />}
                    {it.arrow && <Icon name="arrow-right" size={14} style={{ marginLeft: it.meta ? 6 : "auto", color: "var(--gray-400)", flexShrink: 0 }} />}
                  </button>
                );
              })}
            </div>
          )}
        </div>
        <div className="cmd-foot">
          <span><span className="kbd sm">↑</span><span className="kbd sm">↓</span> navigate</span>
          <span><span className="kbd sm">↵</span> open</span>
          <span><span className="kbd sm">esc</span> close</span>
        </div>
      </div>
    </div>);

}
const cmdItem = { display: "flex", alignItems: "center", gap: 12, padding: "10px 18px", width: "100%", textAlign: "left", fontSize: 14, fontWeight: 500, transition: "background .1s ease" };

// ───────────────── Notification drawer ─────────────────
const NOTIF_SEED = [
{ id: "n1", from: "MC", tone: "success", icon: "check-circle-2", important: true, read: false,
  title: "Recipe Approved!", body: "Your Mediterranean Quinoa Bowl has been approved by the compliance team.",
  ago: "2 minutes ago", time: "6:19 PM", actions: [{ label: "View Recipe", kind: "primary", go: { recipe: "r-001" } }] },
{ id: "n2", from: "AB", tone: "info", icon: "user-plus", important: false, read: false,
  title: "You've received an invitation", body: "Aisha Bello invited you to collaborate on the Spring Menu workspace.",
  ago: "3 minutes ago", time: "6:19 PM", actions: [{ label: "Accept", kind: "primary", go: { page: "assignments", toast: "Invitation accepted — welcome to Spring Menu" } }, { label: "Decline", kind: "ghost", go: { dismiss: true, toast: "Invitation declined" } }] },
{ id: "n3", from: "EN", tone: "violet", icon: "message-square", important: false, read: false,
  title: "Update on your review", body: "Eve Nakamura dropped an update, your submission has new reviewer notes.",
  ago: "18 minutes ago", time: "6:01 PM", actions: [{ label: "View Recipe", kind: "primary", go: { recipe: "r-001" } }] },
{ id: "n4", from: "DL", tone: "warning", icon: "alert-triangle", important: true, read: false,
  title: "Compliance Alert", body: "Keto Fat Burner Smoothie flagged for review. Please address FDA compliance issues.",
  ago: "40 minutes ago", time: "5:39 PM", actions: [{ label: "Review Now", kind: "primary", go: { page: "review-queue", toast: "Opening the review queue" } }, { label: "Dismiss", kind: "danger", go: { dismiss: true, toast: "Alert dismissed" } }] },
{ id: "n5", from: "DL", tone: "error", icon: "x-circle", important: false, read: false,
  title: "Recipe Rejected", body: "Detox Tea Blend does not meet nutritional standards. Please review the feedback.",
  ago: "2 hours ago", time: "4:10 PM", actions: [{ label: "View Comments", kind: "primary", go: { page: "recipes", toast: "Opening reviewer comments" } }, { label: "Edit Recipe", kind: "secondary", go: { page: "edit-recipe" } }] },
{ id: "n6", from: "SY", tone: "brand", icon: "clock", important: false, read: true,
  title: "Deadline Reminder", body: "3 recipes are pending review. Submit before 5 PM today.",
  ago: "5 hours ago", time: "1:00 PM", actions: [{ label: "View Pending", kind: "primary", go: { page: "review-queue" } }] },
{ id: "n7", from: "SY", tone: "neutral", icon: "lightbulb", important: false, read: true,
  title: "Welcome to NutriDMS!", body: "Your account has been successfully created as a Media Contributor.",
  ago: "Yesterday", time: "9:24 AM", actions: [{ label: "Get Started", kind: "primary", go: { page: "dashboard" } }] }];


function injectNotifStyles() {
  if (document.getElementById("notif-styles")) return;
  const css = `
  .notif-bg { position: fixed; inset: 0; background: rgba(10,13,18,.45); z-index: 90; animation: notif-fade .18s ease; }
  @keyframes notif-fade { from { opacity: 0; } to { opacity: 1; } }
  .notif-drawer { position: fixed; top: 0; right: 0; width: 420px; max-width: 100vw; height: 100dvh; background: #fff; box-shadow: -12px 0 40px rgba(14,22,18,.16); display: flex; flex-direction: column; z-index: 91; border-radius: 24px 0 0 24px; overflow: hidden; animation: drawer-in .24s cubic-bezier(.32,.72,.26,1); }
  .notif-head { padding: 18px 20px 0; border-bottom: 1px solid var(--gray-100); }
  .notif-head-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
  .notif-head-top h3 { margin: 0; font-family: var(--serif); font-size: 21px; letter-spacing: -.01em; }
  .notif-tabs { display: flex; align-items: center; gap: 4px; margin-top: 14px; }
  .notif-tab { display: inline-flex; align-items: center; gap: 6px; padding: 9px 12px; border: 0; background: transparent; border-bottom: 2px solid transparent; margin-bottom: -1px; font-family: inherit; font-size: 13px; font-weight: 700; color: var(--gray-500); cursor: pointer; transition: color .12s ease; }
  .notif-tab:hover { color: var(--text-primary); }
  .notif-tab.on { color: var(--green-800); border-bottom-color: var(--green-700); }
  .notif-tab .cnt { font-size: 11px; font-weight: 800; padding: 1px 6px; border-radius: 999px; background: var(--gray-100); color: var(--gray-600); }
  .notif-tab.on .cnt { background: var(--green-100, #E3F0D2); color: var(--green-800); }
  .notif-markall { margin-left: auto; background: transparent; border: 0; color: var(--green-700); font-family: inherit; font-size: 12.5px; font-weight: 700; cursor: pointer; white-space: nowrap; padding: 4px 2px; }
  .notif-markall:hover { color: var(--green-800); text-decoration: underline; }
  .notif-markall:disabled { color: var(--gray-400); cursor: default; text-decoration: none; }
  .notif-list { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 12px; display: flex; flex-direction: column; gap: 10px; }
  .notif-card { position: relative; display: grid; grid-template-columns: 38px 1fr; gap: 12px; padding: 15px 16px 15px 15px; border-radius: 18px; background: #fff; border: 1px solid var(--gray-200); box-shadow: 0 1px 2px rgba(16,24,40,.04); transition: transform .16s cubic-bezier(.32,.72,.26,1), box-shadow .16s ease, border-color .16s ease, background .12s ease; }
  .notif-card:hover { transform: translateY(-2px); box-shadow: 0 10px 24px -10px rgba(16,24,40,.22); border-color: var(--gray-300); }
  .notif-card.unread { background: linear-gradient(90deg, rgba(105,159,42,.06), #fff 62%); border-color: rgba(105,159,42,.28); }
  .notif-card.unread::before { content: ""; position: absolute; left: 8px; top: 20px; width: 6px; height: 6px; border-radius: 50%; background: var(--green-600); }
  .notif-av { width: 38px; height: 38px; border-radius: 50%; display: grid; place-items: center; font-family: var(--serif); font-size: 13px; font-weight: 700; color: #fff; flex-shrink: 0; position: relative; }
  .notif-av.success { background: linear-gradient(135deg, var(--green-600), var(--green-700)); }
  .notif-av.info    { background: linear-gradient(135deg, #2A6FDB, #1F55B0); }
  .notif-av.violet  { background: linear-gradient(135deg, #6938EF, #5925DC); }
  .notif-av.warning { background: linear-gradient(135deg, #DC8A04, #B45309); }
  .notif-av.error   { background: linear-gradient(135deg, #E25555, #C0392B); }
  .notif-av.brand   { background: linear-gradient(135deg, var(--green-500), var(--green-600)); }
  .notif-av.neutral { background: linear-gradient(135deg, #6B7280, #4B5563); }
  .notif-av .badge { position: absolute; right: -3px; bottom: -3px; width: 17px; height: 17px; border-radius: 50%; background: #fff; display: grid; place-items: center; box-shadow: 0 1px 3px rgba(0,0,0,.18); }
  .notif-av.success .badge svg { color: var(--green-700); }
  .notif-av.info .badge svg { color: #2A6FDB; }
  .notif-av.violet .badge svg { color: #6938EF; }
  .notif-av.warning .badge svg { color: #B45309; }
  .notif-av.error .badge svg { color: #C0392B; }
  .notif-av.brand .badge svg { color: var(--green-600); }
  .notif-av.neutral .badge svg { color: #4B5563; }
  .notif-body .t { font-size: 14px; font-weight: 700; color: var(--text-primary); line-height: 1.3; }
  .notif-body .d { margin: 3px 0 0; font-size: 12.75px; color: var(--gray-600); line-height: 1.5; }
  .notif-meta { display: flex; align-items: center; gap: 6px; margin-top: 7px; font-size: 11.5px; color: var(--gray-500); font-weight: 500; white-space: nowrap; }
  .notif-meta span { white-space: nowrap; }
  .notif-meta .sep { width: 3px; height: 3px; border-radius: 50%; background: var(--gray-300); flex-shrink: 0; }
  .notif-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 11px; }
  .notif-btn { display: inline-flex; align-items: center; gap: 5px; padding: 7px 14px; border-radius: 8px; font-family: inherit; font-size: 12.5px; font-weight: 700; cursor: pointer; white-space: nowrap; border: 1.5px solid transparent; transition: all .12s ease; }
  .notif-btn.primary { background: var(--green-700); color: #fff; }
  .notif-btn.primary:hover { background: var(--green-800); }
  .notif-btn.secondary { background: #fff; border-color: var(--green-700); color: var(--green-800); }
  .notif-btn.secondary:hover { background: var(--green-50); }
  .notif-btn.danger { background: #fff; border-color: #FECDCA; color: #B42318; }
  .notif-btn.danger:hover { background: #FEF3F2; }
  .notif-btn.ghost { background: transparent; color: var(--gray-600); }
  .notif-btn.ghost:hover { background: var(--gray-100); color: var(--text-primary); }
  .notif-empty { padding: 48px 24px; text-align: center; color: var(--gray-500); }
  .notif-empty svg { color: var(--gray-300); margin-bottom: 10px; }
  .notif-empty strong { display: block; font-size: 14px; color: var(--gray-700); margin-bottom: 3px; }
  .notif-empty span { font-size: 12.5px; }
  .notif-foot { padding: 12px 16px; border-top: 1px solid var(--gray-100); display: flex; gap: 10px; }
  @media (max-width: 520px) {
    .notif-drawer { width: 100vw; }
    .notif-head { padding: 16px 16px 0; }
    .notif-tabs { gap: 0; flex-wrap: wrap; }
    .notif-tab { padding: 9px 9px; font-size: 12.5px; }
    .notif-markall { width: 100%; margin: 8px 0 0; text-align: left; order: 5; }
  }
  .app[data-theme="dark"] .notif-drawer, .app[data-theme="dark"] .notif-foot, .app[data-theme="dark"] .notif-head { background: #11161B; border-color: #1F242F; }
  `;
  const s = document.createElement("style");s.id = "notif-styles";s.textContent = css;document.head.appendChild(s);
}

function NotificationDrawer({ open, onClose }) {
  injectNotifStyles();
  const app = (typeof useApp === "function" ? useApp() : null) || {};
  const [tab, setTab] = useState("all");
  const [items, setItems] = useState(() => window.NUTRIDMS_PRODUCTION_CLEAN_SLATE ? [] : NOTIF_SEED);
  useEffect(() => {if (open) setTab("all");}, [open]);
  // Live notifications: when the backend is connected, replace the seed with real rows.
  useEffect(() => {
    if (!open || !window.NutriData || !window.NutriData.isConnected()) return;
    let cancelled = false;
    loadNotificationInbox(false).then((res) => {
      if (cancelled || !res) return;
      const rows = Array.isArray(res) ? res : (res.results || []);
      if (!rows.length) return;
      setItems(rows.map((r, i) => ({
        id: r.id || ("n" + i),
        from: (r.actor && (r.actor.initials || r.actor.name)) ? (r.actor.initials || r.actor.name.slice(0, 2)) : "•",
        tone: r.severity === "high" ? "danger" : r.severity === "medium" ? "warn" : "info",
        icon: r.icon || "bell",
        read: !!r.read || !!r.read_at,
        important: r.severity === "high" || !!r.important,
        title: r.title || r.verb || "Notification",
        text: r.body || r.message || "",
        time: (r.created_at || "").slice(0, 10) || "",
        actions: [],
        __remote: true,
      })));
    });
    return () => { cancelled = true; };
  }, [open]);
  if (!open) return null;

  const unreadCount = items.filter((n) => !n.read).length;
  const importantCount = items.filter((n) => n.important).length;
  const shown = items.filter((n) => tab === "all" ? true : tab === "unread" ? !n.read : n.important);
  const markRead = (id) => {
    setItems((xs) => xs.map((n) => n.id === id ? { ...n, read: true } : n));
    if (window.NutriData && window.NutriData.isConnected()) window.NutriData.notifications.markRead(id);
  };
  const markAll = () => {
    setItems((xs) => xs.map((n) => ({ ...n, read: true })));
    if (window.NutriData && window.NutriData.isConnected()) window.NutriData.notifications.readAll();
  };
  const dismiss = (id) => setItems((xs) => xs.filter((n) => n.id !== id));

  const navigate = (go) => {
    if (!go) return;
    if (go.recipe) {
      const list = (typeof window !== "undefined" && window.RECIPES) || [];
      const r = list.find((x) => x.id === go.recipe) || list.find((x) => x.name === go.recipe);
      if (r && window.__openRecipe) { window.__openRecipe(r); }
      else if (app.setPage) { app.setPage("recipes"); }
    } else if (go.page && app.setPage) {
      app.setPage(go.page);
    }
    if (go.toast && app.toast) app.toast(go.toast);
  };

  const runAction = (n, a, e) => {
    if (e) e.stopPropagation();
    const go = a.go || {};
    if (go.dismiss) {
      dismiss(n.id);
      if (go.toast && app.toast) app.toast(go.toast);
      return;
    }
    markRead(n.id);
    navigate(go);
    onClose && onClose();
  };

  return ReactDOM.createPortal(
    <div className="notif-bg" onMouseDown={(e) => {if (e.target === e.currentTarget) onClose();}}>
      <div className="notif-drawer">
        <div className="notif-head">
          <div className="notif-head-top">
            <h3>All Notifications</h3>
            <button className="icon-btn" onClick={onClose} aria-label="Close"><Icon name="x" size={18} /></button>
          </div>
          <div className="notif-tabs">
            <button className={`notif-tab ${tab === "all" ? "on" : ""}`} onClick={() => setTab("all")}>All</button>
            <button className={`notif-tab ${tab === "unread" ? "on" : ""}`} onClick={() => setTab("unread")}>Unread <span className="cnt">{unreadCount}</span></button>
            <button className={`notif-tab ${tab === "important" ? "on" : ""}`} onClick={() => setTab("important")}>Important <span className="cnt">{importantCount}</span></button>
            <button className="notif-markall" onClick={markAll} disabled={unreadCount === 0}>Mark all as read</button>
          </div>
        </div>

        <div className="notif-list">
          {shown.length === 0 ?
          <div className="notif-empty">
              <Icon name="bell-off" size={30} stroke={1.6} />
              <strong>Nothing here</strong>
              <span>{tab === "unread" ? "You're all caught up." : "No notifications in this view."}</span>
            </div> :
          shown.map((n) =>
          <div key={n.id} className={`notif-card ${n.read ? "" : "unread"}`} onMouseDown={() => markRead(n.id)}>
              <div className={`notif-av ${n.tone}`}>
                {n.from}
                <span className="badge"><Icon name={n.icon} size={10} stroke={2.6} /></span>
              </div>
              <div className="notif-body">
                <div className="t">{n.title}</div>
                <p className="d">{n.body}</p>
                <div className="notif-meta"><span>{n.ago}</span><span className="sep" /><span>{n.time}</span></div>
                <div className="notif-actions">
                  {n.actions.map((a, i) =>
                <button key={i} className={`notif-btn ${a.kind}`}
                onClick={(e) => runAction(n, a, e)}>
                      {a.label}
                    </button>
                )}
                </div>
              </div>
            </div>
          )}
        </div>

        <div className="notif-foot">
          <button className="btn secondary" style={{ flex: 1 }} onClick={markAll}><Icon name="check-check" size={16} /> Mark all as read</button>
          <button className="btn ghost" onClick={() => { window.__settingsTab = "notifications"; if (app.setPage) app.setPage("settings"); onClose && onClose(); }}><Icon name="settings" size={15} /> Settings</button>
        </div>
      </div>
      <style>{`@keyframes drawer-in { from { transform: translateX(100%); } to { transform: translateX(0); } }`}</style>
    </div>, document.body);

}

// ───────────────── Export ─────────────────
function ConfirmDialog({ title, body, confirmLabel, cancelLabel, tone, icon, onConfirm, onCancel }) {
  React.useEffect(() => {
    const k = (e) => { if (e.key === "Escape") onCancel && onCancel(); };
    document.addEventListener("keydown", k);
    return () => document.removeEventListener("keydown", k);
  }, [onCancel]);
  const danger = tone === "danger";
  return (
    <div className="confirm-overlay" onMouseDown={(e) => { if (e.target === e.currentTarget) onCancel && onCancel(); }}>
      <div className="confirm-dialog" role="alertdialog" aria-label={title}>
        <div className={`confirm-ic ${danger ? "danger" : ""}`}><Icon name={icon || (danger ? "alert-triangle" : "help-circle")} size={22} /></div>
        <h3 className="confirm-title">{title}</h3>
        {body && <p className="confirm-body">{body}</p>}
        <div className="confirm-actions">
          <button className="btn secondary" onClick={onCancel}>{cancelLabel || "Cancel"}</button>
          <button className={`btn ${danger ? "danger" : "primary"}`} onClick={onConfirm}>{confirmLabel || "Confirm"}</button>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, {
  Icon, BrandMark, AppCtx, useApp, StatusPill, PriorityPill, RefBadge, ConfirmDialog, PersonAvatar,
  Sidebar, Topbar, Crumbs, Stat, ViewToggle, RecipeCard,
  Modal, Toast, CmdPalette, NotificationDrawer, currentUser, NAV_BY_ROLE
});
