/* NutriDMS, Review queue + Users + Roles & Permissions + Bulk Import + Misc */

// ───── Review Queue ─────
function ReviewQueue() {
  const { openRecipe, openIngredient, role } = useApp();
  const [priority, setPriority] = React.useState("all");
  const [kind, setKind] = React.useState("recipe");
  const [advOpen, setAdvOpen] = React.useState(false);
  const [q, setQ] = React.useState("");
  const [adv, setAdv] = React.useState({ status: "", cuisine: "", sort: "recent" });
  const advCount = (adv.status ? 1 : 0) + (adv.cuisine ? 1 : 0) + (adv.sort !== "recent" ? 1 : 0);
  const queue = React.useMemo(() => {
    const wanted = ["pending-review", "compliance-review", "changes-requested", "awaiting-attention", "draft"];
    const norm = (x, kind) => ({
      ...x, _kind: kind,
      contributor: x.contributor || { name: "You", initials: "You" },
      cuisine: x.cuisine || x.canonical || "", category: x.category || "",
      cover: x.cover || x.image || "",
      priority: x.priority || "medium", submitted: x.submitted || "just now",
    });
    let recipes = (window.RECIPES || []).filter((r) => wanted.includes(r.status)).map((r) => norm(r, "recipe"));
    let ings = (typeof kbIngredients === "function" ? kbIngredients() : (window.INGREDIENT_ITEMS || [])).filter((g) => wanted.includes(g.status)).map((g) => norm(g, "ingredient"));
    let subs = [];
    try {
      subs = (window.loadSubmittedItems ? window.loadSubmittedItems("recipe") : []).map((r) => norm(r, "recipe"))
        .concat((window.loadSubmittedItems ? window.loadSubmittedItems("ingredient") : []).map((g) => norm(g, "ingredient")));
    } catch (e) { }
    // de-dupe by id (submitted overrides seed)
    const seen = {}; let list = [];
    subs.concat(recipes, ings).forEach((x) => { if (!seen[x.id]) { seen[x.id] = 1; list.push(x); } });
    if (priority !== "all") list = list.filter((r) => r.priority === priority);
    if (kind !== "all") list = list.filter((r) => r._kind === kind);
    if (adv.status) list = list.filter((r) => r.status === adv.status);
    if (adv.cuisine) list = list.filter((r) => (r.cuisine || "").toLowerCase().includes(adv.cuisine.toLowerCase()));
    if (q.trim()) { const t = q.trim().toLowerCase(); list = list.filter((r) => (r.name || "").toLowerCase().includes(t) || (r.contributor && r.contributor.name || "").toLowerCase().includes(t) || (r.cuisine || "").toLowerCase().includes(t)); }
    if (adv.sort === "priority") list.sort((a, b) => ({ high: 0, medium: 1, low: 2 }[a.priority] - { high: 0, medium: 1, low: 2 }[b.priority]));
    return list;
  }, [priority, role, kind, adv, q]);
  const openItem = (r) => { if (r._kind === "ingredient") { openIngredient && openIngredient(r); } else { openRecipe && openRecipe(r); } };

  return (
    <div>
      <Crumbs path={[{ label: role === "reviewer" ? "Nutrition Review" : "Review Queue" }]} />
      <div className="page-head">
        <div>
          <h1 className="page-title">Review Queue</h1>
          <p className="page-sub">{queue.length} item{queue.length === 1 ? "" : "s"} awaiting your decision · sorted by priority.</p>
        </div>
        <div style={{ display: "flex", gap: 10, alignItems: "center" }}>
          <div className="rq-search">
            <Icon name="search" size={15} stroke={2.2} />
            <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search name, contributor…" />
            {q && <button className="rq-search-x" onClick={() => setQ("")}><Icon name="x" size={13} /></button>}
          </div>
          <button className="btn secondary" onClick={() => setAdvOpen(true)}><Icon name="sliders-horizontal" size={16} /> Advanced Filter{advCount ? ` · ${advCount}` : ""}</button>
        </div>
      </div>

      <div style={{ display: "flex", gap: 8, marginBottom: 14 }}>
        {[{ id: "recipe", label: "Recipe Review", icon: "utensils-crossed" },
        { id: "ingredient", label: "Ingredient Review", icon: "leaf" },
        { id: "all", label: "All", icon: "layers" },
        ].map((t) => (
          <button key={t.id} className={`chip ${kind === t.id ? "on" : ""}`} onClick={() => setKind(t.id)}>
            <Icon name={t.icon} size={14} stroke={2.2} /> {t.label}
          </button>
        ))}
      </div>

      <div style={{ display: "flex", gap: 8, marginBottom: 16 }}>
        {[{ id: "all", label: "All", count: queue.length },
        { id: "high", label: "High", count: queue.filter(r => r.priority === "high").length },
        { id: "medium", label: "Medium", count: queue.filter(r => r.priority === "medium").length },
        { id: "low", label: "Low", count: queue.filter(r => r.priority === "low").length },
        ].map((t) => (
          <button key={t.id} className={`chip ${priority === t.id ? "on" : ""}`} onClick={() => setPriority(t.id)}>
            {t.label} <span style={{ opacity: .7, marginLeft: 4 }}>· {t.count}</span>
          </button>
        ))}
      </div>

      <div className="card" style={{ overflow: "hidden" }}>
        <table className="table">
          <thead><tr>
            <th>{kind === "ingredient" ? "Ingredient" : "Item"}</th>
            <th>Submitted by</th>
            <th>Priority</th>
            <th>Submitted</th>
            <th>Status</th>
            <th>Issues</th>
            <th style={{ textAlign: "right" }}>Action</th>
          </tr></thead>
          <tbody>
            {queue.map((r) => (
              <tr key={r.id} onClick={() => openItem(r)} style={{ cursor: "pointer" }}>
                <td>
                  <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
                    <div className="thumb sm" style={r.cover ? { backgroundImage: `url("${r.cover}")` } : { background: "linear-gradient(135deg,#DCEEC1,#C9E2A4)", display: "grid", placeItems: "center", color: "var(--green-700)" }}>{!r.cover && <Icon name={r._kind === "ingredient" ? "leaf" : "utensils-crossed"} size={16} stroke={1.8} />}</div>
                    <div>
                      <div style={{ fontWeight: 600, color: "var(--text-primary)" }}>{r.name}</div>
                      <div className="muted" style={{ fontSize: 12 }}>{r.cuisine} · {r.category}</div>
                    </div>
                  </div>
                </td>
                <td>
                  <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                    <PersonAvatar person={r.contributor} className="avatar sm" style={{ background: "var(--gray-100)", color: "var(--gray-700)" }} />
                    <span>{r.contributor.name}</span>
                  </div>
                </td>
                <td><PriorityPill priority={r.priority} /></td>
                <td>{formatDate(r.submitted)}</td>
                <td><StatusPill status={r.status} /></td>
                <td>
                  {mockIssues(r).length === 0
                    ? <span className="muted" style={{ fontSize: 13 }}>None</span>
                    : <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
                      {mockIssues(r).map((i, idx) => (
                        <span key={idx} className="pill warning" style={{ fontSize: 11 }}><Icon name="alert-triangle" size={11} stroke={2.4} />{i}</span>
                      ))}
                    </div>
                  }
                </td>
                <td style={{ textAlign: "right" }} onClick={(e) => e.stopPropagation()}>
                  <button className="btn sm primary" onClick={() => openItem(r)}><Icon name="arrow-right" size={14} /> Start Review</button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
        {queue.length === 0 && (
          <div className="empty">
            <div className="icon"><Icon name="check-check" size={24} /></div>
            <h3>Queue is clear</h3>
            <p>You're all caught up, nothing left to review right now.</p>
          </div>
        )}
      </div>

      {advOpen && (
        <div className="rl-modal-scrim" onMouseDown={(e) => e.target === e.currentTarget && setAdvOpen(false)}>
          <div className="rl-modal" role="dialog" aria-modal="true">
            <button className="rl-modal-x" onClick={() => setAdvOpen(false)}><Icon name="x" size={18} /></button>
            <h3 className="rl-modal-t">Advanced Filter</h3>
            <p className="rl-modal-sub">Narrow the {kind === "all" ? "" : kind + " "}review queue.</p>
            <div className="rl-mgrid rl-modal-grid">
              <label className="rl-mfield">
                <span>Status</span>
                <select value={adv.status} onChange={(e) => setAdv({ ...adv, status: e.target.value })}>
                  <option value="">Any status</option>
                  <option value="pending-review">Pending review</option>
                  <option value="compliance-review">In compliance</option>
                  <option value="changes-requested">Changes requested</option>
                  <option value="draft">Draft</option>
                </select>
              </label>
              <label className="rl-mfield">
                <span>Cuisine / category</span>
                <input value={adv.cuisine} onChange={(e) => setAdv({ ...adv, cuisine: e.target.value })} placeholder="e.g. Mediterranean" />
              </label>
              <label className="rl-mfield">
                <span>Sort by</span>
                <select value={adv.sort} onChange={(e) => setAdv({ ...adv, sort: e.target.value })}>
                  <option value="recent">Recently submitted</option>
                  <option value="priority">Priority</option>
                </select>
              </label>
            </div>
            <div className="rl-modal-foot">
              <button className="btn secondary" onClick={() => setAdv({ status: "", cuisine: "", sort: "recent" })}>Clear</button>
              <button className="btn primary" onClick={() => setAdvOpen(false)}><Icon name="filter" size={15} /> Apply Filter</button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}
function mockIssues(r) {
  // Generate "real" inline issues per recipe to surface what was hidden behind a modal in the original design
  if (r.id === "r-001") return ["Cover image flagged"];
  if (r.id === "r-003") return ["Allergen tag missing", "Sugar high"];
  if (r.id === "r-006") return ["Image quality"];
  if (r.id === "r-007") return [];
  return [];
}

// ───── Users ─────
const ROLE_PALETTE = { "media-contributor": "#3b7c0f", "reviewer": "#2A54E5", "compliance": "#6938EF", "manager": "#dc6803", "admin": "#0E9384", "super-admin": "#b42318" };
function rolePalette(r) { return ROLE_PALETTE[r] || "#667085"; }

function UsersScreen() {
  const { toast, role } = useApp();
  const [directory, setDirectory] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [loadError, setLoadError] = React.useState("");
  const [q, setQ] = React.useState("");
  const [filterRole, setFilterRole] = React.useState("all");
  const [inviteOpen, setInviteOpen] = React.useState(false);
  const [sending, setSending] = React.useState(false);
  const [form, setForm] = React.useState({ name: "", email: "", role: "", department: "" });
  const [tab, setTab] = React.useState("directory");
  const [profile, setProfile] = React.useState(null);
  const [kpiFilter, setKpiFilter] = React.useState(null);
  const [menu, setMenu] = React.useState(null);
  const [selectedInvite, setSelectedInvite] = React.useState(null);
  const [busyAction, setBusyAction] = React.useState("");

  const canManage = ["admin", "super-admin"].includes(role);
  const heading = role === "manager" ? "Team Members" : "User Management";

  const uiRoleFromApi = (value) => {
    const key = String(value || "").toLowerCase().replace(/-/g, "_");
    if (["super_admin", "superadmin"].includes(key)) return "super-admin";
    if (key === "admin") return "admin";
    if (key === "editorial_manager") return "manager";
    if (key === "reviewer") return "reviewer";
    if (key === "compliance_officer") return "compliance";
    return "media-contributor";
  };
  const safeRole = (person) => ROLES[person.role] || {
    label: person.roleLabel || "Member",
    color: "neutral",
  };
  const momentLabel = (value, emptyLabel) => {
    if (!value) return emptyLabel || "—";
    const time = new Date(value).getTime();
    if (!Number.isFinite(time)) return emptyLabel || "—";
    const elapsed = Math.max(0, Date.now() - time);
    const minutes = Math.floor(elapsed / 60000);
    if (minutes < 1) return "Now";
    if (minutes < 60) return minutes + " min ago";
    const hours = Math.floor(minutes / 60);
    if (hours < 24) return hours + " hr ago";
    const days = Math.floor(hours / 24);
    if (days < 30) return days + " d ago";
    return Math.floor(days / 30) + " mo ago";
  };
  const progressLabel = (value) => ({
    invitation_sent: "Invitation sent",
    invitation_opened: "Invitation opened",
    registration_started: "Registration started",
    email_verified: "Email verified",
    profile_completed: "Profile completed",
    accepted: "Joined organization",
  }[value] || "Invitation queued");
  const mapPerson = (item) => {
    const invited = item.record_type === "invitation";
    const mappedRole = uiRoleFromApi(item.role && item.role.key);
    return {
      ...item,
      role: mappedRole,
      roleId: item.role && item.role.id,
      roleLabel: (item.role && item.role.label) || "Member",
      departmentName: (item.department && item.department.name) || "No department",
      departmentId: (item.department && item.department.id) || "",
      departmentManagerId: (item.department && item.department.manager_id) || "",
      departmentManagerName: (item.department && (item.department.manager_name || item.department.manager_email)) || "Not assigned",
      permissionOverrides: item.permission_overrides || {},
      effectiveUiPermissions: item.effective_ui_permissions || [],
      mfaRequired: !!item.mfa_required,
      sessions: Number(item.active_sessions || 0),
      profilePictureUrl: item.profile_picture_url || "",
      status: invited ? "invited" : item.status,
      rawStatus: item.status,
      lastActive: invited
        ? (item.sent_at ? momentLabel(item.sent_at) : "Queued")
        : momentLabel(item.last_active_at, item.status === "active" ? "Active" : "—"),
      current: String(item.email || "").toLowerCase() === String((window.__nutridmsAuthenticatedUser || {}).email || "").toLowerCase(),
    };
  };

  const loadDirectory = React.useCallback(async (force) => {
    if (!window.NutriAPI) {
      setLoadError("The NutriDMS API client is not available.");
      setLoading(false);
      return;
    }
    setLoading(true);
    setLoadError("");
    try {
      const coordinator = window.NutriWorkspaceRefresh;
      const payload = coordinator && coordinator.fetch
        ? await coordinator.fetch("people:directory", () => window.NutriAPI.get("/invitations/directory/"), { scope: "org", ttlMs: coordinator.DEFAULT_TTL_MS, force: force === true })
        : await window.NutriAPI.get("/invitations/directory/");
      const next = {
        organization: payload.organization || null,
        summary: payload.summary || {},
        people: Array.isArray(payload.people) ? payload.people : [],
        roles: Array.isArray(payload.roles) ? payload.roles : [],
        departments: Array.isArray(payload.departments) ? payload.departments : [],
      };
      setDirectory(next);
      setProfile((current) => {
        if (!current) return current;
        const fresh = next.people.find((item) => item.record_type === "member" && String(item.id) === String(current.id));
        return fresh ? mapPerson(fresh) : current;
      });
      setForm((current) => {
        const firstRole = next.roles.find((item) => item.key === "media_contributor")
          || next.roles.find((item) => !["super_admin", "super-admin"].includes(item.key))
          || next.roles[0];
        const firstDepartment = next.departments.find((item) => String(item.name || "").toLowerCase() === "nutrition")
          || next.departments[0];
        return {
          ...current,
          role: current.role || (firstRole && firstRole.id) || "",
          department: current.department || (firstDepartment && firstDepartment.id) || "",
        };
      });
    } catch (error) {
      setLoadError((error && error.message) || "Could not load the people directory.");
    } finally {
      setLoading(false);
    }
  }, []);

  React.useEffect(() => {
    const coordinator = window.NutriWorkspaceRefresh;
    const refresh = () => {
      if (coordinator) coordinator.invalidate("people:directory", { scope: "org" });
      loadDirectory(true);
    };
    const applyCoordinatorValue = () => loadDirectory(false);
    if (coordinator && coordinator.register) {
      coordinator.register("people:directory", () => window.NutriAPI.get("/invitations/directory/"), {
        scope: "org",
        active: true,
        onValue: () => window.dispatchEvent(new Event("nutridms-directory-data"))
      });
      coordinator.activate("people:directory", true);
    }
    loadDirectory(false);
    window.addEventListener("nutridms-organization-switched", refresh);
    window.addEventListener("nutridms-directory-data", applyCoordinatorValue);
    return () => {
      if (coordinator && coordinator.activate) coordinator.activate("people:directory", false);
      window.removeEventListener("nutridms-organization-switched", refresh);
      window.removeEventListener("nutridms-directory-data", applyCoordinatorValue);
    };
  }, [loadDirectory]);

  React.useEffect(() => {
    if (!menu) return undefined;
    const close = () => setMenu(null);
    const onKey = (event) => { if (event.key === "Escape") close(); };
    window.addEventListener("resize", close);
    window.addEventListener("scroll", close, true);
    document.addEventListener("keydown", onKey);
    return () => {
      window.removeEventListener("resize", close);
      window.removeEventListener("scroll", close, true);
      document.removeEventListener("keydown", onKey);
    };
  }, [menu]);

  const people = React.useMemo(
    () => ((directory && directory.people) || []).map(mapPerson),
    [directory],
  );
  const roles = (directory && directory.roles) || [];
  const departments = (directory && directory.departments) || [];
  const summary = (directory && directory.summary) || {};
  const noMfa = (person) => person.record_type === "member" && person.status === "active" && person.mfa_enabled === false;
  const stale = (person) => {
    if (person.record_type !== "member" || person.status !== "active" || !person.last_active_at) return false;
    return Date.now() - new Date(person.last_active_at).getTime() >= 30 * 86400000;
  };
  const kpiMatch = (person) => !kpiFilter
    || (kpiFilter === "active" && person.status === "active")
    || (kpiFilter === "invited" && person.record_type === "invitation")
    || (kpiFilter === "inactive" && person.record_type === "member" && person.status !== "active")
    || (kpiFilter === "nomfa" && noMfa(person))
    || (kpiFilter === "stale" && stale(person));
  const list = people
    .filter((person) => {
      const query = q.trim().toLowerCase();
      return !query
        || String(person.name || "").toLowerCase().includes(query)
        || String(person.email || "").toLowerCase().includes(query);
    })
    .filter((person) => filterRole === "all" || String(person.roleId) === filterRole)
    .filter(kpiMatch);

  const cfg = (typeof kbLoadConfig === "function")
    ? kbLoadConfig()
    : { metric: "count", weights: { high: 3, medium: 2, low: 1 }, includeFinal: false, underMax: 2, overMin: 6, hardCap: 9 };
  const workload = React.useMemo(() => {
    const values = {};
    try {
      const projects = JSON.parse(localStorage.getItem("nutridms_kanban_projects") || "[]");
      projects.forEach((project) => {
        const finals = (project.cols || []).filter((column) => column.done).map((column) => column.id);
        Object.entries(project.board || {}).forEach(([column, cards]) => {
          if (!cfg.includeFinal && finals.includes(column)) return;
          (cards || []).forEach((card) => {
            if (!card.assignee) return;
            const weight = cfg.metric === "weighted" ? (cfg.weights[card.priority] || 1) : 1;
            values[card.assignee.initials] = (values[card.assignee.initials] || 0) + weight;
          });
        });
      });
    } catch (error) { }
    return values;
  }, []);
  const classify = (value) => value === 0 ? "idle" : value <= cfg.underMax ? "under" : value >= cfg.overMin ? "high" : "ok";
  const statusText = (value) => value === "idle" ? "Available" : value === "under" ? "Light load" : value === "high" ? "Overloaded" : "Balanced";
  const maxW = Math.max(cfg.hardCap, ...Object.values(workload), 1);

  const openActions = (person, event) => {
    event.stopPropagation();
    const box = event.currentTarget.getBoundingClientRect();
    const height = person.record_type === "invitation" ? 188 : 92;
    const top = box.bottom + 8 + height > window.innerHeight ? Math.max(12, box.top - height - 8) : box.bottom + 8;
    setMenu({
      person,
      top,
      left: Math.max(12, Math.min(window.innerWidth - 236, box.right - 224)),
    });
  };

  const sendInvite = async () => {
    const name = form.name.trim();
    const email = form.email.trim().toLowerCase();
    const nameParts = name.split(/\s+/).filter(Boolean);
    if (nameParts.length < 2) {
      toast("Enter the member's first and last name.");
      return;
    }
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
      toast("Enter a valid email address.");
      return;
    }
    if (!form.role) {
      toast("Choose a role.");
      return;
    }
    setSending(true);
    try {
      await window.NutriAPI.post("/invitations/", {
        first_name: nameParts[0],
        last_name: nameParts.slice(1).join(" "),
        email,
        role: form.role,
        department: form.department || null,
      });
      setInviteOpen(false);
      setForm((current) => ({ ...current, name: "", email: "" }));
      toast("Invitation sent to " + name + ".");
      await loadDirectory(true);
      try { window.dispatchEvent(new Event("nutridms-members-changed")); } catch (error) { }
    } catch (error) {
      toast((error && error.message) || "The invitation could not be sent.");
    } finally {
      setSending(false);
    }
  };

  const invitationAction = async (action, person) => {
    if (!person || person.record_type !== "invitation") return;
    if (action === "revoke" && !window.confirm("Revoke the invitation for " + person.email + "? The current link will stop working.")) return;
    setMenu(null);
    setBusyAction(action + ":" + person.id);
    try {
      if (action === "resend") {
        await window.NutriAPI.post("/invitations/" + encodeURIComponent(person.id) + "/resend/", {});
        toast("Invitation resent to " + person.email + ".");
      } else if (action === "copy") {
        const result = await window.NutriAPI.post("/invitations/" + encodeURIComponent(person.id) + "/rotate-link/", {});
        if (!result || !result.invite_url) throw new Error("A new invitation link was not returned.");
        await navigator.clipboard.writeText(result.invite_url);
        toast("A new invitation link was copied. The previous link is no longer valid.");
      } else if (action === "revoke") {
        await window.NutriAPI.del("/invitations/" + encodeURIComponent(person.id) + "/");
        toast("Invitation revoked.");
        setSelectedInvite(null);
      }
      await loadDirectory(true);
    } catch (error) {
      toast((error && error.message) || "The invitation action could not be completed.");
    } finally {
      setBusyAction("");
    }
  };

  const activeAction = busyAction && selectedInvite && busyAction.endsWith(":" + selectedInvite.id);
  const kpis = [
    { k: null, label: "Total members", value: summary.total_people || 0, icon: "users", tone: "" },
    { k: "active", label: "Active", value: summary.active || 0, icon: "circle-check", tone: "ok" },
    { k: "invited", label: "Pending invites", value: summary.pending || 0, icon: "mail", tone: "info" },
    { k: "inactive", label: "Inactive", value: summary.inactive || 0, icon: "moon", tone: "" },
    { k: "stale", label: "Inactive 30+ days", value: summary.inactive_30_days || 0, icon: "clock", tone: "warn" },
    { k: "nomfa", label: "Without MFA", value: summary.without_mfa || 0, icon: "shield-off", tone: "warn" },
  ];

  return (
    <div className="enterprise-users">
      <Crumbs path={[{ label: heading }]} />
      <div className="page-head">
        <div>
          <h1 className="page-title">{heading}</h1>
          <p className="page-sub">
            {people.length} {people.length === 1 ? "member" : "members"} · {summary.active || 0} active
            {(summary.pending || 0) > 0 ? " · " + summary.pending + " pending" : ""}
          </p>
        </div>
        {canManage && (
          <button className="btn primary" onClick={() => setInviteOpen(true)} disabled={loading}>
            <Icon name="user-plus" size={16} /> Add member
          </button>
        )}
      </div>

      <div className="kb-viewseg" style={{ marginBottom: 16 }}>
        <button className={tab === "directory" ? "on" : ""} onClick={() => setTab("directory")}><Icon name="users" size={15} /> <span className="kb-viewseg-l">Directory</span></button>
        <button className={tab === "workload" ? "on" : ""} onClick={() => setTab("workload")}><Icon name="gauge" size={15} /> <span className="kb-viewseg-l">Workload</span></button>
      </div>

      {tab === "directory" && (
        <div className="um-kpis">
          {kpis.map((card) => (
            <button
              key={card.label}
              className={"um-kpi" + (kpiFilter === card.k && card.k ? " on" : "") + (card.tone ? " " + card.tone : "")}
              onClick={() => setKpiFilter(kpiFilter === card.k ? null : card.k)}
            >
              <span className="um-kpi-ic"><Icon name={card.icon} size={15} /></span>
              <span className="um-kpi-v">{card.value}</span>
              <span className="um-kpi-l">{card.label}</span>
            </button>
          ))}
        </div>
      )}

      {loadError && (
        <div className="card" role="alert" style={{ padding: 14, marginBottom: 16, borderColor: "#fecdca", background: "#fffbfa", display: "flex", justifyContent: "space-between", alignItems: "center", gap: 16 }}>
          <span style={{ color: "#b42318" }}>{loadError}</span>
          <button className="btn secondary sm" onClick={loadDirectory}>Retry</button>
        </div>
      )}

      {tab === "workload" ? (
        <div className="kb-load-wrap">
          <div className="kb-load-legend">
            <span><i className="kb-load-sw idle" /> Available (0)</span>
            <span><i className="kb-load-sw under" /> Light (≤{cfg.underMax})</span>
            <span><i className="kb-load-sw ok" /> Balanced</span>
            <span><i className="kb-load-sw high" /> Overloaded (≥{cfg.overMin})</span>
            <span className="kb-load-measuring">Across all boards · measuring {cfg.metric === "weighted" ? "weighted by priority" : "item count"}</span>
          </div>
          {people.filter((person) => person.record_type === "member" && person.status === "active").sort((a, b) => (workload[b.initials] || 0) - (workload[a.initials] || 0)).map((person) => {
            const value = workload[person.initials] || 0;
            const loadClass = classify(value);
            return (
              <div key={person.id} className={"kb-person " + loadClass}>
                <div className="kb-person-l">
                  <span className="kb-av" style={person.profilePictureUrl ? { backgroundImage: `url("${person.profilePictureUrl}")`, backgroundSize: "cover", backgroundPosition: "center" } : { background: rolePalette(person.role) }}>{!person.profilePictureUrl && person.initials}</span>
                  <div className="kb-person-id"><div className="kb-person-name">{person.name}</div><div className="kb-person-role">{safeRole(person).label}</div></div>
                </div>
                <div className="kb-person-mid">
                  <div className="kb-person-barrow">
                    <span className="kb-load-bar"><i className={loadClass} style={{ width: Math.min(100, (value / maxW) * 100) + "%" }} /></span>
                    <span className="kb-person-num">{value}<span>/{cfg.hardCap}</span></span>
                  </div>
                  <div className="kb-person-items"><span className="kb-person-empty">{value === 0 ? "No active assignments, ready for new work." : value + " active workload items across boards"}</span></div>
                </div>
                <div className="kb-person-r">
                  <span className={"kb-load-count " + loadClass}><Icon name={loadClass === "high" ? "alert-triangle" : loadClass === "idle" ? "circle-check" : "check"} size={12} /> {statusText(loadClass)}</span>
                  <button className="btn secondary sm" onClick={() => { window.__setPage && window.__setPage("assignments"); }}><Icon name="external-link" size={13} /> Open board</button>
                </div>
              </div>
            );
          })}
        </div>
      ) : (
        <>
          <div className="card" style={{ padding: 12, marginBottom: 16, display: "flex", gap: 12, flexWrap: "wrap", alignItems: "center" }}>
            <div className="search" style={{ flex: 1, minWidth: 240 }}>
              <Icon name="search" size={16} />
              <input value={q} onChange={(event) => setQ(event.target.value)} placeholder="Search members…" />
            </div>
            <select className="select" style={{ width: 260 }} value={filterRole} onChange={(event) => setFilterRole(event.target.value)}>
              <option value="all">All roles</option>
              {roles.map((item) => <option key={item.id} value={String(item.id)}>{item.label}</option>)}
            </select>
          </div>

          <div className="card" style={{ overflow: "hidden" }}>
            {loading && !directory ? (
              <div className="empty"><div className="icon"><Icon name="loader-circle" size={24} /></div><h3>Loading people</h3><p>Syncing your organization directory.</p></div>
            ) : (
              <table className="table">
                <thead><tr>
                  <th>Member</th>
                  <th>Role</th>
                  <th>Department</th>
                  <th>Status</th>
                  <th>Last active</th>
                  <th><span className="sr-only">Actions</span></th>
                </tr></thead>
                <tbody>
                  {list.map((person) => {
                    const roleMeta = safeRole(person);
                    const invited = person.record_type === "invitation";
                    return (
                      <tr key={person.record_type + ":" + person.id} onClick={() => invited ? setSelectedInvite(person) : setProfile(person)} style={{ cursor: "pointer" }}>
                        <td>
                          <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
                            <div className="avatar" style={person.profilePictureUrl ? { backgroundImage: `url("${person.profilePictureUrl}")`, backgroundSize: "cover", backgroundPosition: "center" } : { background: rolePalette(person.role) }}>{!person.profilePictureUrl && (person.initials || "?")}</div>
                            <div>
                              <div style={{ fontWeight: 600, color: "var(--text-primary)" }}>{person.name}</div>
                              <div className="muted" style={{ fontSize: 12 }}>{person.email}</div>
                            </div>
                          </div>
                        </td>
                        <td><span className={"pill " + roleMeta.color}>{person.roleLabel || roleMeta.label}</span></td>
                        <td>{person.departmentName}</td>
                        <td>
                          {person.status === "active" && <span className="pill success">Active</span>}
                          {person.status === "invited" && (
                            <div><span className="pill info">Pending</span><div className="muted" style={{ fontSize: 11, marginTop: 3 }}>{progressLabel(person.progress_status)}</div></div>
                          )}
                          {!["active", "invited"].includes(person.status) && <span className="pill neutral">{String(person.status || "Inactive").replace(/_/g, " ")}</span>}
                        </td>
                        <td>{person.lastActive}</td>
                        <td>
                          <div style={{ display: "flex", gap: 4, justifyContent: "flex-end" }} onClick={(event) => event.stopPropagation()}>
                            {!invited && <button className="icon-btn" title="Edit member" onClick={() => setProfile(person)}><Icon name="pencil" size={14} /></button>}
                            <button
                              className="icon-btn"
                              title={invited ? "Invitation actions" : "Member actions"}
                              aria-haspopup="menu"
                              aria-expanded={!!(menu && menu.person.id === person.id)}
                              onClick={(event) => openActions(person, event)}
                            >
                              <Icon name="more-horizontal" size={14} />
                            </button>
                          </div>
                        </td>
                      </tr>
                    );
                  })}
                  {!list.length && (
                    <tr><td colSpan="6"><div className="empty"><div className="icon"><Icon name="users" size={24} /></div><h3>No people found</h3><p>Try another search or role filter.</p></div></td></tr>
                  )}
                </tbody>
              </table>
            )}
          </div>
        </>
      )}

      <Modal
        open={inviteOpen}
        onClose={() => !sending && setInviteOpen(false)}
        title="Add a team member"
        subtitle="They'll get an email with a magic-link sign-in."
        footer={
          <>
            <button className="btn ghost" onClick={() => setInviteOpen(false)} disabled={sending}>Cancel</button>
            <button className="btn primary" disabled={sending || !form.name.trim() || !form.email.trim() || !form.role} onClick={sendInvite}>
              <Icon name={sending ? "loader-circle" : "send"} size={14} /> {sending ? "Sending…" : "Send invite"}
            </button>
          </>
        }
      >
        <div className="col" style={{ gap: 14 }}>
          <div className="field"><label>Full name</label><input className="input" autoFocus value={form.name} onChange={(event) => setForm((current) => ({ ...current, name: event.target.value }))} placeholder="e.g. Maya Roberts" /></div>
          <div className="field"><label>Email</label><input className="input" type="email" value={form.email} onChange={(event) => setForm((current) => ({ ...current, email: event.target.value }))} placeholder="teammate@nutridms.io" /></div>
          <div className="field"><label>Role</label>
            <select className="select" value={form.role} onChange={(event) => setForm((current) => ({ ...current, role: event.target.value }))}>
              {roles.filter((item) => !["super_admin", "super-admin"].includes(item.key)).map((item) => <option key={item.id} value={item.id}>{item.label}</option>)}
            </select>
          </div>
          <div className="field"><label>Department</label>
            <select className="select" value={form.department} onChange={(event) => setForm((current) => ({ ...current, department: event.target.value }))}>
              <option value="">No department</option>
              {departments.map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
            </select>
          </div>
        </div>
      </Modal>

      <Modal
        open={!!selectedInvite}
        onClose={() => !activeAction && setSelectedInvite(null)}
        title="Invitation details"
        subtitle={selectedInvite ? selectedInvite.email : ""}
        footer={selectedInvite && canManage ? (
          <>
            <button className="btn ghost" onClick={() => invitationAction("revoke", selectedInvite)} disabled={!!activeAction} style={{ color: "#b42318" }}>Revoke</button>
            <button className="btn secondary" onClick={() => invitationAction("copy", selectedInvite)} disabled={!!activeAction}><Icon name="link" size={14} /> Copy new link</button>
            <button className="btn primary" onClick={() => invitationAction("resend", selectedInvite)} disabled={!!activeAction}><Icon name="send" size={14} /> Resend</button>
          </>
        ) : null}
      >
        {selectedInvite && (
          <div className="col" style={{ gap: 10 }}>
            <div className="um-row"><span className="um-row-k">Member</span><span className="um-row-v">{selectedInvite.name}</span></div>
            <div className="um-row"><span className="um-row-k">Role</span><span className="um-row-v">{selectedInvite.roleLabel}</span></div>
            <div className="um-row"><span className="um-row-k">Department</span><span className="um-row-v">{selectedInvite.departmentName}</span></div>
            <div className="um-row"><span className="um-row-k">Progress</span><span className="um-row-v">{progressLabel(selectedInvite.progress_status)}</span></div>
            <div className="um-row"><span className="um-row-k">Delivery</span><span className="um-row-v">{String(selectedInvite.delivery_status || "queued").replace(/_/g, " ")}</span></div>
            <div className="um-row"><span className="um-row-k">Sent</span><span className="um-row-v">{momentLabel(selectedInvite.sent_at, "Queued")}</span></div>
            <div className="um-row"><span className="um-row-k">Expires</span><span className="um-row-v">{selectedInvite.expires_at ? new Date(selectedInvite.expires_at).toLocaleString() : "—"}</span></div>
          </div>
        )}
      </Modal>

      {menu && ReactDOM.createPortal(
        <>
          <button
            aria-label="Close actions menu"
            onClick={() => setMenu(null)}
            style={{ position: "fixed", inset: 0, zIndex: 1198, border: 0, padding: 0, background: "transparent", cursor: "default" }}
          />
          <div
            role="menu"
            aria-label={menu.person.record_type === "invitation" ? "Invitation actions" : "Member actions"}
            className="card"
            style={{ position: "fixed", top: menu.top, left: menu.left, width: 224, zIndex: 1199, padding: 6, boxShadow: "0 16px 40px rgba(16,24,40,.18)" }}
          >
            {menu.person.record_type === "invitation" ? (
              <>
                <button role="menuitem" className="btn ghost" style={{ width: "100%", justifyContent: "flex-start" }} onClick={() => { setSelectedInvite(menu.person); setMenu(null); }}><Icon name="eye" size={15} /> View invitation</button>
                {canManage && <button role="menuitem" className="btn ghost" style={{ width: "100%", justifyContent: "flex-start" }} onClick={() => invitationAction("resend", menu.person)}><Icon name="send" size={15} /> Resend invitation</button>}
                {canManage && <button role="menuitem" className="btn ghost" style={{ width: "100%", justifyContent: "flex-start" }} onClick={() => invitationAction("copy", menu.person)}><Icon name="link" size={15} /> Copy new invite link</button>}
                {canManage && <button role="menuitem" className="btn ghost" style={{ width: "100%", justifyContent: "flex-start", color: "#b42318" }} onClick={() => invitationAction("revoke", menu.person)}><Icon name="trash-2" size={15} /> Revoke invitation</button>}
              </>
            ) : (
              <>
                <button role="menuitem" className="btn ghost" style={{ width: "100%", justifyContent: "flex-start" }} onClick={() => { setProfile(menu.person); setMenu(null); }}><Icon name="user-round" size={15} /> View profile</button>
                {canManage && <button role="menuitem" className="btn ghost" style={{ width: "100%", justifyContent: "flex-start" }} onClick={() => { setProfile(menu.person); setMenu(null); }}><Icon name="shield" size={15} /> Edit access</button>}
              </>
            )}
          </div>
        </>,
        document.body,
      )}

      {profile && <UserProfileDrawer u={profile} onClose={() => setProfile(null)} workload={workload[profile.initials] || 0} canManage={canManage} toast={toast} roles={roles} departments={departments} people={people} onChanged={() => loadDirectory(true)} />}
    </div>
  );
}

// ───── Member profile drawer ─────
function UserProfileDrawer({ u, onClose, workload, canManage, toast, roles, departments, people, onChanged }) {
  const [t, setT] = React.useState("overview");
  const [member, setMember] = React.useState(u);
  const [busy, setBusy] = React.useState("");
  const [activity, setActivity] = React.useState([]);
  const [activityError, setActivityError] = React.useState("");
  const [statusOpen, setStatusOpen] = React.useState(false);
  const [statusForm, setStatusForm] = React.useState({
    status: "suspended", temporary: false, date: "", workload_policy: "keep", workload_delegate: "",
  });
  React.useEffect(() => setMember(u), [u]);
  const noMfa = !member.mfa_enabled;
  const r = ROLES[member.role] || { label: member.roleLabel || member.role, color: "neutral" };
  const tabs = [["overview", "Overview"], ["access", "Roles & access"], ["assignments", "Assignments"], ["security", "Security"], ["activity", "Activity"]];
  const Row = ({ k, v, tone }) => (
    <div className="um-row"><span className="um-row-k">{k}</span><span className={"um-row-v" + (tone ? " " + tone : "")}>{v}</span></div>
  );
  const go = (page) => { onClose(); window.__setPage && window.__setPage(page); };
  const roleUiKey = (value) => {
    const key = String(value || "").toLowerCase().replace(/-/g, "_");
    if (["super_admin", "superadmin"].includes(key)) return "super-admin";
    if (key === "admin") return "admin";
    if (key === "editorial_manager") return "manager";
    if (key === "reviewer") return "reviewer";
    if (key === "compliance_officer") return "compliance";
    return "media-contributor";
  };
  const mutate = async (label, call, success) => {
    if (busy) return null;
    setBusy(label);
    try {
      const result = await call();
      if (success) toast(success);
      await onChanged();
      return result;
    } catch (error) {
      toast((error && error.message) || "The change could not be applied.");
      return null;
    } finally {
      setBusy("");
    }
  };
  const updateMember = (patch, message) => mutate(
    "member",
    () => window.NutriAPI.patch("/invitations/members/" + encodeURIComponent(member.id) + "/", patch),
    message,
  );
  const changeDepartmentManager = (departmentId, managerId) => mutate(
    "department",
    () => window.NutriAPI.patch("/invitations/departments/" + encodeURIComponent(departmentId) + "/", { manager: managerId || null }),
    "Department manager updated.",
  );
  const security = (operation, message) => mutate(
    "security",
    () => window.NutriAPI.post("/invitations/members/" + encodeURIComponent(member.id) + "/security/", { operation }),
    message,
  );
  const loadActivity = React.useCallback(async (force) => {
    try {
      const coordinator = window.NutriWorkspaceRefresh;
      const key = "people:activity:" + encodeURIComponent(member.id);
      const loader = () => window.NutriAPI.get("/invitations/members/" + encodeURIComponent(member.id) + "/activity/");
      const payload = coordinator && coordinator.fetch
        ? await coordinator.fetch(key, loader, { scope: "org", ttlMs: coordinator.DEFAULT_TTL_MS, force: force === true })
        : await loader();
      setActivity(Array.isArray(payload && payload.results) ? payload.results : []);
      setActivityError("");
    } catch (error) {
      setActivityError((error && error.message) || "Activity could not be loaded.");
    }
  }, [member.id]);
  React.useEffect(() => {
    if (t !== "activity") return undefined;
    const coordinator = window.NutriWorkspaceRefresh;
    const key = "people:activity:" + encodeURIComponent(member.id);
    const eventName = "nutridms-member-activity";
    const loader = () => window.NutriAPI.get("/invitations/members/" + encodeURIComponent(member.id) + "/activity/");
    const applyCoordinatorValue = () => loadActivity(false);
    if (coordinator && coordinator.register) {
      coordinator.register(key, loader, { scope: "org", active: true, onValue: () => window.dispatchEvent(new Event(eventName)) });
      coordinator.activate(key, true);
    }
    loadActivity(false);
    window.addEventListener(eventName, applyCoordinatorValue);
    return () => {
      if (coordinator && coordinator.activate) coordinator.activate(key, false);
      window.removeEventListener(eventName, applyCoordinatorValue);
    };
  }, [t, loadActivity]);
  const permissionItems = PERMISSIONS.flatMap((group) => group.items.map((item) => ({ ...item, group: group.group })));
  const personalValue = (key) => Object.prototype.hasOwnProperty.call(member.permissionOverrides || {}, key)
    ? ((member.permissionOverrides || {})[key] ? "allow" : "deny")
    : "inherit";
  const setPersonalPermission = async (key, value) => {
    const next = { ...(member.permissionOverrides || {}) };
    if (value === "inherit") delete next[key];
    else next[key] = value === "allow";
    setMember((current) => ({ ...current, permissionOverrides: next }));
    const result = await updateMember({ permission_overrides: next }, "Personal access updated immediately.");
    if (!result) setMember(member);
  };
  const eligibleManagers = (people || []).filter((person) => person.record_type === "member" && person.status === "active" && person.role === "manager");
  const delegates = (people || []).filter((person) => person.record_type === "member" && person.status === "active" && String(person.id) !== String(member.id));
  const submitStatus = async () => {
    if (statusForm.temporary && !statusForm.date) {
      toast("Choose the date this temporary status should end.");
      return;
    }
    if (statusForm.workload_policy === "reassign" && !statusForm.workload_delegate) {
      toast("Choose who should receive the workload.");
      return;
    }
    const payload = {
      status: statusForm.status,
      temporary_until: statusForm.temporary && statusForm.status !== "deactivated"
        ? new Date(statusForm.date + "T23:59:59").toISOString()
        : null,
      workload_policy: statusForm.workload_policy,
      workload_delegate: statusForm.workload_policy === "reassign" ? statusForm.workload_delegate : null,
    };
    const result = await mutate(
      "status",
      () => window.NutriAPI.post("/invitations/members/" + encodeURIComponent(member.id) + "/status/", payload),
      member.name + " access status updated.",
    );
    if (result) setStatusOpen(false);
  };
  const reactivate = () => mutate(
    "status",
    () => window.NutriAPI.post("/invitations/members/" + encodeURIComponent(member.id) + "/status/", {
      status: "active", workload_policy: "keep", workload_delegate: null, temporary_until: null,
    }),
    member.name + " reactivated.",
  );
  const activityLabel = (row) => String(row.action || "activity")
    .replace(/^member\./, "")
    .replace(/^auth\./, "")
    .replace(/[._]/g, " ")
    .replace(/\b\w/g, (letter) => letter.toUpperCase());
  const activityTime = (value) => {
    if (!value) return "";
    const seconds = Math.max(0, Math.floor((Date.now() - new Date(value).getTime()) / 1000));
    if (seconds < 60) return "now";
    if (seconds < 3600) return Math.floor(seconds / 60) + " min ago";
    if (seconds < 86400) return Math.floor(seconds / 3600) + " hr ago";
    return Math.floor(seconds / 86400) + " d ago";
  };

  return (
    <>
      <div className="um-drawer-scrim" onMouseDown={(e) => e.target === e.currentTarget && onClose()}>
        <div className="um-drawer">
          <div className="um-drawer-head">
            <div style={{ display: "flex", alignItems: "center", gap: 12, minWidth: 0 }}>
              <div className="avatar lg" style={member.profilePictureUrl ? { backgroundImage: `url("${member.profilePictureUrl}")`, backgroundSize: "cover", backgroundPosition: "center" } : { background: rolePalette(member.role) }}>{!member.profilePictureUrl && member.initials}</div>
              <div style={{ minWidth: 0 }}>
                <div className="um-drawer-name">{member.name}</div>
                <div className="um-drawer-email">{member.email}</div>
              </div>
            </div>
            <button className="icon-btn" onClick={onClose}><Icon name="x" size={18} /></button>
          </div>
          <div className="um-drawer-badges">
            <span className={`pill ${r.color}`}>{member.roleLabel || r.label}</span>
            {member.status === "active" && <span className="pill success">Active</span>}
            {member.status === "suspended" && <span className="pill error">Suspended</span>}
            {member.status === "on_hold" && <span className="pill warning">On hold</span>}
            {member.status === "deactivated" && <span className="pill neutral">Deactivated</span>}
            {noMfa && member.status === "active" && <span className="pill warning"><Icon name="shield-off" size={11} /> No MFA</span>}
            {member.mfaRequired && noMfa && <span className="pill info">MFA required</span>}
          </div>
          <div className="um-drawer-tabs">
            {tabs.map(([id, label]) => <button key={id} className={t === id ? "on" : ""} onClick={() => setT(id)}>{label}</button>)}
          </div>
          <div className="um-drawer-body">
            {t === "overview" && <>
              <div className="um-sec-h">Identity</div>
              {canManage ? (
                <label className="um-edit"><span>Department</span>
                  <select className="select sm" value={member.departmentId || ""} disabled={!!busy} onChange={(event) => updateMember(
                    { department: event.target.value || null },
                    member.name + " moved to " + ((departments.find((item) => String(item.id) === event.target.value) || {}).name || "no department") + ".",
                  )}>
                    <option value="">No department</option>
                    {departments.map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
                  </select>
                </label>
              ) : <Row k="Department" v={member.departmentName} />}
              {member.departmentId && canManage ? (
                <label className="um-edit"><span>Department manager</span>
                  <select className="select sm" value={member.departmentManagerId || ""} disabled={!!busy} onChange={(event) => changeDepartmentManager(member.departmentId, event.target.value)}>
                    <option value="">Not assigned</option>
                    {eligibleManagers.map((person) => <option key={person.id} value={person.id}>{person.name} · Editorial Manager</option>)}
                  </select>
                </label>
              ) : <Row k="Department manager" v={member.departmentManagerName || "Not assigned"} />}
              <Row k="Account type" v={member.accountType || "Internal employee"} />
              <Row k="Last active" v={member.lastActive} />
              <Row k="Subscription seat" v="Assigned" tone="ok" />
              <div className="um-sec-h">Current workload</div>
              <Row k="Open assignments" v={workload + " item" + (workload === 1 ? "" : "s")} tone={workload >= 6 ? "warn" : "ok"} />
              {canManage && workload > 0 && <button className="btn secondary sm" style={{ marginTop: 10 }} onClick={() => go("assignments")}><Icon name="shuffle" size={13} /> Rebalance workload</button>}
            </>}
            {t === "access" && <>
              <div className="um-sec-h">Primary role</div>
              {canManage ? (
                <label className="um-edit"><span>Role</span>
                  <select className="select sm" value={member.roleId || ""} disabled={!!busy} onChange={(event) => {
                    const role = roles.find((item) => String(item.id) === event.target.value);
                    updateMember({ role: event.target.value }, member.name + " is now " + (role ? role.label : "updated") + ".");
                  }}>
                    {roles.map((item) => <option key={item.id} value={item.id}>{item.label}</option>)}
                  </select>
                </label>
              ) : <Row k="Role" v={member.roleLabel || r.label} />}
              <Row k="Scope" v="Organization-wide · server enforced" tone="ok" />
              <div className="um-sec-h">Personal access</div>
              <p className="muted" style={{ fontSize: 12.5, margin: "0 0 10px" }}>Overrides take effect immediately. Inherit follows the role matrix.</p>
              {permissionItems.map((item) => (
                <label className="um-edit" key={item.key}>
                  <span><small className="muted" style={{ display: "block", fontSize: 10 }}>{item.group}</small>{item.label}</span>
                  <select className="select sm" style={{ width: 116 }} value={personalValue(item.key)} disabled={!canManage || !!busy} onChange={(event) => setPersonalPermission(item.key, event.target.value)}>
                    <option value="inherit">Inherit</option>
                    <option value="allow">Allow</option>
                    <option value="deny">Deny</option>
                  </select>
                </label>
              ))}
            </>}
            {t === "assignments" && <>
              <div className="um-sec-h">Assigned work</div>
              <button className="um-row um-row-btn" onClick={() => go("review-queue")}><span className="um-row-k">Reviews</span><span className="um-row-v">{workload} active <Icon name="chevron-right" size={13} /></span></button>
              <button className="um-row um-row-btn" onClick={() => go("assignments")}><span className="um-row-k">Open tasks</span><span className={"um-row-v" + (workload >= 6 ? " warn" : "")}>{workload} active <Icon name="chevron-right" size={13} /></span></button>
              {canManage && <button className="btn secondary sm" style={{ marginTop: 12 }} onClick={() => go("assignments")}><Icon name="external-link" size={13} /> Transfer work on board</button>}
            </>}
            {t === "security" && <>
              <div className="um-sec-h">Authentication</div>
              <Row k="MFA" v={member.mfa_enabled ? "Enabled" : (member.mfaRequired ? "Required · enrollment pending" : "Not enabled")} tone={member.mfa_enabled ? "ok" : "warn"} />
              <Row k="Failed sign-ins" v={String(member.failed_sign_ins || 0)} tone="ok" />
              <Row k="Active sessions" v={(member.sessions || 0) + " devices"} />
              {canManage && <div style={{ display: "flex", gap: 8, marginTop: 12, flexWrap: "wrap" }}>
                {!member.mfaRequired
                  ? <button className="btn secondary sm" disabled={!!busy} onClick={() => security("require_mfa", "MFA is now required for " + member.name + ".")}><Icon name="shield" size={13} /> Require MFA</button>
                  : <button className="btn secondary sm" disabled={!!busy} onClick={() => security("remove_mfa_requirement", "MFA requirement removed.")}><Icon name="shield-off" size={13} /> Remove requirement</button>}
                <button className="btn secondary sm" disabled={!!busy} onClick={() => window.confirm("Force sign-out on every device for " + member.name + "?") && security("force_sign_out", member.name + " was signed out of every device.")}><Icon name="log-out" size={13} /> Force sign-out</button>
                <button className="btn secondary sm" disabled={!!busy} onClick={() => window.confirm("Send a password reset code to " + member.email + "?") && security("reset_password", "Password reset sent to " + member.email + ".")}><Icon name="key-round" size={13} /> Reset password</button>
              </div>}
            </>}
            {t === "activity" && <>
              <div className="um-sec-h">Recent activity · live</div>
              {activityError && <p style={{ color: "#b42318", fontSize: 12.5 }}>{activityError}</p>}
              {!activityError && !activity.length && <p className="muted" style={{ fontSize: 12.5 }}>No activity has been recorded yet.</p>}
              {activity.map((row) => (
                <div className="um-act" key={row.id}>
                  <Icon name="activity" size={13} />
                  <span>{activityLabel(row)} · {activityTime(row.created_at)}</span>
                </div>
              ))}
              <button className="btn ghost sm" style={{ marginTop: 10 }} onClick={() => go("audit")}><Icon name="scroll-text" size={13} /> View full audit history</button>
            </>}
          </div>
          {canManage && <div className="um-drawer-foot">
            <button className="btn ghost" onClick={onClose}>Close</button>
            <button className="btn secondary" disabled={!!busy} onClick={() => mutate(
              "review",
              () => window.NutriAPI.post("/invitations/members/" + encodeURIComponent(member.id) + "/review/", {}),
              "Access review recorded for " + member.name + ".",
            )}>Mark reviewed</button>
            {member.status !== "active"
              ? <button className="btn primary" disabled={!!busy} onClick={reactivate}>Reactivate</button>
              : <button className="btn danger" disabled={!!busy} onClick={() => setStatusOpen(true)}>Change status</button>}
          </div>}
        </div>
      </div>
      <Modal
        open={statusOpen}
        onClose={() => !busy && setStatusOpen(false)}
        title={"Change " + member.name + "’s status"}
        subtitle="Access and workload changes are applied immediately."
        footer={<>
          <button className="btn ghost" disabled={!!busy} onClick={() => setStatusOpen(false)}>Cancel</button>
          <button className="btn danger" disabled={!!busy} onClick={submitStatus}>{busy === "status" ? "Applying…" : "Apply status"}</button>
        </>}
      >
        <div className="col" style={{ gap: 14 }}>
          <div className="field"><label>Status</label>
            <select className="select" value={statusForm.status} onChange={(event) => setStatusForm((current) => ({ ...current, status: event.target.value, temporary: event.target.value === "deactivated" ? false : current.temporary }))}>
              <option value="suspended">Suspend access</option>
              <option value="on_hold">On hold · vacation</option>
              <option value="deactivated">Deactivate permanently</option>
            </select>
          </div>
          {statusForm.status !== "deactivated" && <label style={{ display: "flex", gap: 9, alignItems: "center" }}>
            <input type="checkbox" checked={statusForm.temporary} onChange={(event) => setStatusForm((current) => ({ ...current, temporary: event.target.checked }))} />
            Restore access automatically on a date
          </label>}
          {statusForm.temporary && statusForm.status !== "deactivated" && <div className="field"><label>Restore date</label><input className="input" type="date" min={new Date().toISOString().slice(0, 10)} value={statusForm.date} onChange={(event) => setStatusForm((current) => ({ ...current, date: event.target.value }))} /></div>}
          <div className="field"><label>Current workload</label>
            <select className="select" value={statusForm.workload_policy} onChange={(event) => setStatusForm((current) => ({ ...current, workload_policy: event.target.value }))}>
              <option value="keep">Keep assignments as they are</option>
              <option value="reassign">Move open reviews to another person</option>
            </select>
          </div>
          {statusForm.workload_policy === "reassign" && <div className="field"><label>Reassign to</label>
            <select className="select" value={statusForm.workload_delegate} onChange={(event) => setStatusForm((current) => ({ ...current, workload_delegate: event.target.value }))}>
              <option value="">Choose a person</option>
              {delegates.map((person) => <option key={person.id} value={person.id}>{person.name} · {person.roleLabel}</option>)}
            </select>
          </div>}
        </div>
      </Modal>
    </>
  );
}

// ───── Roles & Permissions ─────
function PermissionsScreen() {
  const { toast } = useApp();
  const cloneDefaults = () => PERMISSIONS.map((group) => ({
    ...group,
    items: group.items.map((item) => ({ ...item, roles: [...item.roles] })),
  }));
  const [data, setData] = React.useState(cloneDefaults);
  const [roleCounts, setRoleCounts] = React.useState({});
  const [loading, setLoading] = React.useState(true);
  const [busyKey, setBusyKey] = React.useState("");

  const applyMatrix = React.useCallback((matrix) => {
    setData(PERMISSIONS.map((group) => ({
      ...group,
      items: group.items.map((item) => ({
        ...item,
        roles: matrix && Array.isArray(matrix[item.key]) ? matrix[item.key] : [...item.roles],
      })),
    })));
  }, []);
  const loadPermissions = React.useCallback(async (quiet, force) => {
    if (!window.NutriAPI) return;
    if (!quiet) setLoading(true);
    try {
      const coordinator = window.NutriWorkspaceRefresh;
      const loader = () => window.NutriAPI.get("/invitations/access-control/");
      const payload = coordinator && coordinator.fetch
        ? await coordinator.fetch("people:access-control", loader, { scope: "org", ttlMs: coordinator.DEFAULT_TTL_MS, force: force === true })
        : await loader();
      applyMatrix(payload && payload.role_permissions || {});
      setRoleCounts(payload && payload.role_counts || {});
    } catch (error) {
      if (!quiet) toast((error && error.message) || "Role permissions could not be loaded.");
    } finally {
      if (!quiet) setLoading(false);
    }
  }, [applyMatrix, toast]);
  React.useEffect(() => {
    loadPermissions(false);
    const coordinator = window.NutriWorkspaceRefresh;
    const refresh = () => {
      if (coordinator) coordinator.invalidate("people:access-control", { scope: "org" });
      loadPermissions(true, true);
    };
    const applyCoordinatorValue = () => loadPermissions(true, false);
    if (coordinator && coordinator.register) {
      coordinator.register("people:access-control", () => window.NutriAPI.get("/invitations/access-control/"), {
        scope: "org", active: true, onValue: () => window.dispatchEvent(new Event("nutridms-access-control-data"))
      });
      coordinator.activate("people:access-control", true);
    }
    window.addEventListener("nutridms-access-changed", refresh);
    window.addEventListener("nutridms-access-control-data", applyCoordinatorValue);
    return () => {
      if (coordinator && coordinator.activate) coordinator.activate("people:access-control", false);
      window.removeEventListener("nutridms-access-changed", refresh);
      window.removeEventListener("nutridms-access-control-data", applyCoordinatorValue);
    };
  }, [loadPermissions]);

  const resetPermissions = async () => {
    if (!window.confirm("Reset every role permission to the NutriDMS defaults?")) return;
    setBusyKey("reset");
    try {
      const payload = await window.NutriAPI.patch("/invitations/access-control/", { reset: true });
      applyMatrix(payload && payload.role_permissions || {});
      try {
        localStorage.removeItem("nutridms_perms");
        window.dispatchEvent(new Event("nutridms-perms"));
        window.dispatchEvent(new Event("nutridms-nav"));
      } catch (error) { }
      toast("Permissions reset and applied.");
    } catch (error) {
      toast((error && error.message) || "Permissions could not be reset.");
    } finally {
      setBusyKey("");
    }
  };

  const togglePerm = async (groupIdx, key, roleId) => {
    if (busyKey) return;
    const current = data[groupIdx].items.find((item) => item.key === key);
    const allowed = !current.roles.includes(roleId);
    const nextRoles = allowed ? [...current.roles, roleId] : current.roles.filter((value) => value !== roleId);
    if (key === "roles_edit" && roleId === "super-admin" && !allowed) {
      toast("Super Admin must retain Roles & Permissions access.");
      return;
    }
    setBusyKey(key + ":" + roleId);
    setData((rows) => rows.map((group, index) => index !== groupIdx ? group : ({
      ...group,
      items: group.items.map((item) => item.key !== key ? item : ({
        ...item,
        roles: allowed ? [...item.roles, roleId] : item.roles.filter((value) => value !== roleId),
      })),
    })));
    try {
      await window.NutriAPI.patch("/invitations/access-control/", {
        permission_key: key,
        role_key: roleId,
        allowed,
        roles: nextRoles,
      });
      const currentUser = window.__nutridmsAuthenticatedUser;
      if (currentUser) {
        const matrix = { ...(currentUser.rolePermissionMatrix || {}) };
        const roles = (matrix[key] || current.roles || []).filter((value) => value !== roleId);
        if (allowed) roles.push(roleId);
        currentUser.rolePermissionMatrix = { ...matrix, [key]: [...new Set(roles)] };
      }
      window.dispatchEvent(new Event("nutridms-perms"));
      window.dispatchEvent(new Event("nutridms-nav"));
      window.dispatchEvent(new Event("nutridms-access-changed"));
      toast("Permission applied immediately.");
    } catch (error) {
      if (window.NutriWorkspaceRefresh) window.NutriWorkspaceRefresh.invalidate("people:access-control", { scope: "org" });
      await loadPermissions(true, true);
      toast((error && error.message) || "Permission change failed.");
    } finally {
      setBusyKey("");
    }
  };

  return (
    <div>
      <Crumbs path={[{ label: "Roles & Permissions" }]} />
      <div className="page-head">
        <div>
          <h1 className="page-title">Roles & Permissions</h1>
          <p className="page-sub">Server-enforced access. Every change is applied to active users immediately.</p>
        </div>
        <div style={{ display: "flex", gap: 10, alignItems: "center" }}>
          <span className="pill success"><Icon name="radio" size={13} /> Live · auto-saved</span>
          <button className="btn secondary" disabled={!!busyKey || loading} onClick={resetPermissions}><Icon name="rotate-ccw" size={16} /> Reset to defaults</button>
        </div>
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 16, marginBottom: 24 }}>
        {Object.entries(ROLES).map(([id, role]) => (
          <div key={id} className="card pad">
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 12 }}>
              <div>
                <span className={`pill ${role.color}`}>{role.label}</span>
                <div className="muted" style={{ marginTop: 8, fontSize: 13, lineHeight: 1.5 }}>{role.description}</div>
              </div>
              <div style={{ textAlign: "right" }}>
                <div style={{ fontSize: 22, fontWeight: 700 }}>{roleCounts[id] || 0}</div>
                <div className="muted" style={{ fontSize: 11, fontWeight: 600 }}>user{(roleCounts[id] || 0) === 1 ? "" : "s"}</div>
              </div>
            </div>
          </div>
        ))}
      </div>

      <div className="card" style={{ overflow: "hidden", opacity: loading ? .72 : 1 }}>
        <table className="table">
          <thead>
            <tr>
              <th style={{ width: "32%" }}>Permission</th>
              {Object.entries(ROLES).map(([id, role]) => (
                <th key={id} style={{ textAlign: "center" }}><span className={`pill ${role.color}`} style={{ fontSize: 10 }}>{role.label.split(" ")[0]}</span></th>
              ))}
            </tr>
          </thead>
          <tbody>
            {data.map((group, groupIdx) => (
              <React.Fragment key={group.group}>
                <tr><td colSpan={1 + Object.keys(ROLES).length} style={{ background: "var(--gray-50)", padding: "10px 16px", fontWeight: 700, fontSize: 12, textTransform: "uppercase", letterSpacing: ".08em", color: "var(--gray-600)" }}>{group.group}</td></tr>
                {group.items.map((item) => (
                  <tr key={item.key}>
                    <td><strong>{item.label}</strong></td>
                    {Object.keys(ROLES).map((roleId) => {
                      const selected = item.roles.includes(roleId);
                      const saving = busyKey === item.key + ":" + roleId;
                      return (
                        <td key={roleId} style={{ textAlign: "center" }}>
                          <button
                            onClick={() => togglePerm(groupIdx, item.key, roleId)}
                            aria-pressed={selected}
                            aria-label={(selected ? "Remove " : "Grant ") + item.label + " for " + ROLES[roleId].label}
                            disabled={!!busyKey || loading}
                            style={{
                              width: 22, height: 22, borderRadius: 6,
                              border: selected ? "1px solid var(--green-700)" : "1.5px solid var(--gray-300)",
                              background: selected ? "var(--green-700)" : "#fff",
                              display: "grid", placeItems: "center", opacity: saving ? .55 : 1,
                              transition: "all .12s ease",
                            }}
                          >
                            {selected && <Icon name="check" size={14} stroke={2.8} style={{ color: "#fff" }} />}
                          </button>
                        </td>
                      );
                    })}
                  </tr>
                ))}
              </React.Fragment>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

// ───── Bulk Import (CSV) ─────
function BulkImportScreen({ embedded }) {
  const { toast } = useApp();
  const fileRef = React.useRef(null);
  const rulesFileRef = React.useRef(null);
  const [jobs, setJobs] = React.useState([]);
  const [selectedId, setSelectedId] = React.useState("");
  const [state, setState] = React.useState("loading");
  const [uploading, setUploading] = React.useState(false);
  const [busy, setBusy] = React.useState("");
  const selected = jobs.find((job) => String(job.id) === String(selectedId)) || jobs[0] || null;

  const load = React.useCallback(async (silent) => {
    if (!window.NutriSettings || !window.NutriSettings.ingredientImports) {
      setState("error");
      return;
    }
    if (!silent) setState("loading");
    try {
      const payload = await window.NutriSettings.ingredientImports();
      const rows = (payload && payload.results) || (Array.isArray(payload) ? payload : []);
      setJobs(rows);
      setSelectedId((current) => current || (rows[0] && rows[0].id) || "");
      setState("ready");
    } catch (error) {
      setState("error");
      if (!silent) toast((error && error.message) || "Import jobs could not be loaded");
    }
  }, [toast]);

  React.useEffect(() => {
    load(false);
  }, [load]);

  React.useEffect(() => {
    if (!selected || !["queued", "processing"].includes(selected.job_status)) return;
    const timer = window.setInterval(() => load(true), 2500);
    return () => window.clearInterval(timer);
  }, [selected && selected.id, selected && selected.job_status, load]);

  const upload = async (file) => {
    if (!file || uploading) return;
    const name = String(file.name || "").toLowerCase();
    if (!name.endsWith(".csv") && !name.endsWith(".pdf")) {
      toast("Choose a CSV or PDF ingredient catalogue.");
      return;
    }
    setUploading(true);
    try {
      const job = await window.NutriSettings.uploadIngredientImport(file);
      setSelectedId(job.id);
      toast(`“${file.name}” is queued for review.`);
      await load(true);
    } catch (error) {
      toast((error && error.message) || "The import could not be started");
    } finally {
      setUploading(false);
      if (fileRef.current) fileRef.current.value = "";
    }
  };
  const updateRow = async (row, patch) => {
    if (!selected) return;
    setBusy("row:" + row.id);
    try {
      const next = await window.NutriSettings.updateIngredientImportRow(selected.id, row.id, patch);
      setJobs((current) => current.map((job) => job.id !== selected.id ? job : {
        ...job,
        rows: (job.rows || []).map((item) => item.id === row.id ? next : item),
      }));
    } catch (error) {
      toast((error && error.message) || "This import row could not be updated");
    } finally {
      setBusy("");
    }
  };
  const commit = async () => {
    if (!selected) return;
    setBusy("commit");
    try {
      const result = await window.NutriSettings.commitIngredientImport(selected.id);
      toast(`${result.committed || 0} ingredient${result.committed === 1 ? "" : "s"} imported as drafts.`);
      await load(true);
    } catch (error) {
      toast((error && error.message) || "Resolve all flagged rows before committing this import");
    } finally {
      setBusy("");
    }
  };
  const uploadRules = async (file) => {
    if (!file || !window.NutriCompliance) return;
    if (!String(file.name || "").toLowerCase().endsWith(".csv")) {
      toast("Choose a CSV file for compliance rules.");
      return;
    }
    setBusy("rules");
    try {
      const result = await window.NutriCompliance.bulkUpload(await file.text(), file.name);
      toast(`${result.imported || 0} compliance rule${result.imported === 1 ? "" : "s"} imported.`);
    } catch (error) {
      toast((error && error.message) || "Compliance rules could not be imported");
    } finally {
      setBusy("");
      if (rulesFileRef.current) rulesFileRef.current.value = "";
    }
  };
  const reviewRows = (selected && selected.rows) || [];
  const needsReview = reviewRows.filter((row) => row.review_status === "needs_review").length;

  return (
    <div>
      {!embedded && <Crumbs path={[{ label: "Bulk Import" }]} />}
      <div className="page-head">
        <div>
          <h1 className="page-title">Bulk Import, CSV</h1>
          <p className="page-sub">Stage ingredient catalogues for review before drafts are created. CSV and PDF sources are retained in the import audit trail.</p>
        </div>
        <button className="btn secondary" onClick={() => load(false)} disabled={state === "loading"}><Icon name="refresh-cw" size={16} /> Refresh</button>
      </div>

      <div className="card pad" style={{ marginBottom: 16 }}>
        <input ref={fileRef} type="file" accept=".csv,.pdf,text/csv,application/pdf" style={{ display: "none" }} onChange={(event) => upload(event.target.files && event.target.files[0])} />
        <div style={{ display: "flex", gap: 14, alignItems: "center", flexWrap: "wrap" }}>
          <div style={{ flex: 1, minWidth: 240 }}><strong>Ingredient catalogue</strong><div className="muted" style={{ fontSize: 12.5, marginTop: 3 }}>CSV headers may include name, quantity, unit, supplier, category, allergens, notes and reference ID. PDF imports are extracted for review.</div></div>
          <button className="btn primary" disabled={uploading} onClick={() => fileRef.current && fileRef.current.click()}><Icon name="upload-cloud" size={15} /> {uploading ? "Uploading…" : "Upload CSV or PDF"}</button>
        </div>
        <div className="refid-note" style={{ marginTop: 14 }}><Icon name="shield-check" size={15} /><span>No ingredient is created until its staged rows have been reviewed and committed. Accepted imports create drafts, not published catalogue records.</span></div>
      </div>

      <div className="card pad" style={{ marginBottom: 16 }}>
        <input ref={rulesFileRef} type="file" accept=".csv,text/csv" style={{ display: "none" }} onChange={(event) => uploadRules(event.target.files && event.target.files[0])} />
        <div style={{ display: "flex", gap: 14, alignItems: "center", flexWrap: "wrap" }}>
          <div style={{ flex: 1, minWidth: 240 }}><strong>Compliance rules</strong><div className="muted" style={{ fontSize: 12.5, marginTop: 3 }}>Import a rules CSV through the validated compliance bulk-import endpoint.</div></div>
          <button className="btn secondary" disabled={busy === "rules"} onClick={() => rulesFileRef.current && rulesFileRef.current.click()}><Icon name="file-spreadsheet" size={15} /> {busy === "rules" ? "Importing…" : "Import rules CSV"}</button>
        </div>
      </div>

      {state === "error" ? <div className="alert warning"><Icon name="alert-triangle" size={17} /><div>Import jobs are unavailable. Confirm you have ingredient creation access, then refresh.</div></div>
        : state === "loading" ? <div className="card pad" style={{ textAlign: "center", padding: 48 }}><Icon name="loader-circle" size={30} /><div style={{ marginTop: 10 }}>Loading import jobs…</div></div>
          : <>
            {jobs.length > 0 && <div className="card pad" style={{ marginBottom: 16 }}>
              <div className="field"><label>Import job</label><select className="select" value={selected ? selected.id : ""} onChange={(event) => setSelectedId(event.target.value)}>{jobs.map((job) => <option key={job.id} value={job.id}>{job.original_name} · {job.job_status} · {job.row_count || 0} rows</option>)}</select></div>
            </div>}
            {!selected ? <div className="empty" style={{ padding: "42px 20px" }}><div className="icon"><Icon name="file-spreadsheet" size={24} /></div><h3>No imports yet</h3><p>Upload an ingredient catalogue to start a reviewed import.</p></div>
              : <div className="card pad">
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 12, marginBottom: 15, flexWrap: "wrap" }}>
                  <div><h3 style={{ fontFamily: "var(--serif)", fontSize: 21, margin: 0 }}>{selected.original_name}</h3><p className="muted" style={{ margin: "4px 0 0" }}>{selected.job_status === "review" ? `${reviewRows.length} staged rows · ${needsReview} need a decision` : selected.job_status === "committed" ? `${selected.summary && selected.summary.committed || 0} drafts created` : `Status: ${selected.job_status}`}</p></div>
                  {selected.job_status === "review" && <button className="btn primary" disabled={busy === "commit" || needsReview > 0} onClick={commit}><Icon name="check" size={14} /> {busy === "commit" ? "Committing…" : `Commit ${reviewRows.filter((row) => row.review_status !== "rejected").length} draft${reviewRows.filter((row) => row.review_status !== "rejected").length === 1 ? "" : "s"}`}</button>}
                </div>
                {selected.job_status === "failed" && <div className="alert warning"><Icon name="alert-triangle" size={17} /><div>{selected.summary && selected.summary.error || "The import failed before any ingredients were created."}</div></div>}
                {["queued", "processing"].includes(selected.job_status) && <div className="alert info"><Icon name="loader-circle" size={17} /><div>Parsing is in progress. This page refreshes automatically until rows are ready for review.</div></div>}
                {selected.job_status === "review" && <>
                  {needsReview > 0 && <div className="alert warning" style={{ marginBottom: 14 }}><Icon name="alert-triangle" size={17} /><div>Accept or exclude every flagged row before committing. Server validation still runs at commit time.</div></div>}
                  <div style={{ overflowX: "auto" }}><table className="table"><thead><tr><th>#</th><th>Ingredient</th><th>Reference ID</th><th>Match</th><th>Validation</th><th>Decision</th></tr></thead><tbody>{reviewRows.map((row) => <tr key={row.id}><td>{row.row_number}</td><td><strong>{row.normalized && row.normalized.name || "Unnamed ingredient"}</strong><div className="muted" style={{ fontSize: 11.5 }}>{row.normalized && row.normalized.supplier || "No supplier"}</div></td><td>{row.normalized && row.normalized.reference_id || "Auto-generated"}</td><td>{row.canonical_name ? `${row.canonical_name} (${Math.round((row.confidence || 0) * 100)}%)` : "No canonical match"}</td><td>{(row.errors || []).length ? <div style={{ display: "grid", gap: 4 }}>{row.errors.map((error, index) => <span className="pill error" key={index}>{error}</span>)}</div> : <span className={`pill ${row.review_status === "needs_review" ? "warning" : "success"}`}>{row.review_status.replace("_", " ")}</span>}</td><td>{row.review_status === "needs_review" ? <div style={{ display: "flex", gap: 6 }}><button className="btn secondary sm" disabled={busy === "row:" + row.id} onClick={() => updateRow(row, { review_status: "accepted" })}>Accept</button><button className="btn ghost sm" disabled={busy === "row:" + row.id} onClick={() => updateRow(row, { review_status: "rejected" })}>Exclude</button></div> : <span className="muted" style={{ fontSize: 12 }}>{row.review_status}</span>}</td></tr>)}</tbody></table></div>
                </>}
              </div>}
          </>}
    </div>
  );
}

// ───── Settings (stub) ─────
// ───── Settings (in-app, role-aware) ─────
function setGet(key, def) { try { const v = localStorage.getItem("nutridms_set_" + key); return v == null ? def : JSON.parse(v); } catch (e) { return def; } }
function setPut(key, v) { try { localStorage.setItem("nutridms_set_" + key, JSON.stringify(v)); } catch (e) { } }
function useStored(key, def) {
  const [v, setV] = React.useState(() => setGet(key, def));
  const update = (nv) => { setV(nv); setPut(key, nv); };
  return [v, update];
}

const SETTINGS_TABS = {
  "media-contributor": [
    { id: "profile", t: "Profile", ic: "user" },
    { id: "submission", t: "Submission Preferences", ic: "utensils-crossed" },
    { id: "notifications", t: "Notifications", ic: "bell" },
    { id: "appearance", t: "Appearance", ic: "palette" },
    { id: "security", t: "Password & Security", ic: "key-round" },
  ],
  "reviewer": [
    { id: "profile", t: "Profile", ic: "user" },
    { id: "review", t: "Review Preferences", ic: "clipboard-check" },
    { id: "notifications", t: "Notifications", ic: "bell" },
    { id: "appearance", t: "Appearance", ic: "palette" },
    { id: "security", t: "Password & Security", ic: "key-round" },
  ],
  "compliance": [
    { id: "profile", t: "Profile", ic: "user" },
    { id: "compliance", t: "Compliance Defaults", ic: "shield-check" },
    { id: "notifications", t: "Notifications", ic: "bell" },
    { id: "appearance", t: "Appearance", ic: "palette" },
    { id: "security", t: "Password & Security", ic: "key-round" },
  ],
  "manager": [
    { id: "profile", t: "Profile", ic: "user" },
    { id: "editorial", t: "Editorial & Team", ic: "users" },
    { id: "notifications", t: "Notifications", ic: "bell" },
    { id: "appearance", t: "Appearance", ic: "palette" },
    { id: "security", t: "Password & Security", ic: "key-round" },
  ],
  "admin": [
    { id: "profile", t: "Profile", ic: "user" },
    { id: "organization", t: "Organization", ic: "building-2" },
    { id: "refids", t: "Reference IDs", ic: "hash" },
    { id: "workflow", t: "Publishing Workflow", ic: "git-branch" },
    { id: "loadbalancing", t: "Load Balancing", ic: "scale" },
    { id: "bulkimport", t: "Bulk Import (CSV)", ic: "file-spreadsheet" },
    { id: "recyclebin", t: "Recycle Bin", ic: "trash-2" },
    { id: "access", t: "Security & Access", ic: "lock" },
    { id: "loraa-messenger", t: "Loraa Messenger", ic: "message-circle" },
    { id: "notifications", t: "Notifications", ic: "bell" },
    { id: "appearance", t: "Appearance", ic: "palette" },
  ],
  "super-admin": [
    { id: "profile", t: "Profile", ic: "user" },
    { id: "organization", t: "Organization", ic: "building-2" },
    { id: "refids", t: "Reference IDs", ic: "hash" },
    { id: "workflow", t: "Publishing Workflow", ic: "git-branch" },
    { id: "loadbalancing", t: "Load Balancing", ic: "scale" },
    { id: "bulkimport", t: "Bulk Import (CSV)", ic: "file-spreadsheet" },
    { id: "recyclebin", t: "Recycle Bin", ic: "trash-2" },
    { id: "access", t: "Security & Access", ic: "lock" },
    { id: "platform", t: "Platform", ic: "server" },
    { id: "loraa-messenger", t: "Loraa Messenger", ic: "message-circle" },
    { id: "notifications", t: "Notifications", ic: "bell" },
    { id: "appearance", t: "Appearance", ic: "palette" },
  ],
};

/* Compliance moved into Settings, appended to every role's tab list as a
   grouped section, ordered as a guided setup sequence. Edit actions inside each
   page stay gated to Admin / Super-admin. */
const COMPLIANCE_SETTINGS_TABS = [
  { sec: "Compliance", secHint: "Set up in this order" },
  { id: "allergen-table", t: "Allergen Table", ic: "triangle-alert", step: 1 },
  { id: "health-conditions", t: "Health Conditions", ic: "heart-pulse", step: 2 },
  { id: "health-tag-rules", t: "Health Tag Rules", ic: "tag", step: 3 },
  { id: "nutrient-rules", t: "Nutrient Rules", ic: "file-text", step: 4 },
  { id: "ingredient-rules", t: "Ingredient Swap & Alternative Rules", ic: "leaf", step: 5 },
  { id: "recipe-table", t: "Recipe Table", ic: "table-2", step: 6 },
  { id: "compliance-publishing", t: "Compliance & Publishing", ic: "shield-check", step: 7 },
];
Object.keys(SETTINGS_TABS).forEach((r) => {
  const tabs = SETTINGS_TABS[r];
  if (!tabs.find((t) => t.id === "security")) {
    const notificationIndex = tabs.findIndex((t) => t.id === "notifications");
    tabs.splice(notificationIndex >= 0 ? notificationIndex : tabs.length, 0, { id: "security", t: "Password & Security", ic: "key-round" });
  }
  // Subscription & Features, second tab for every role (after Profile)
  if (!tabs.find((t) => t.id === "subscription")) {
    const pi = tabs.findIndex((t) => t.id === "profile");
    tabs.splice(pi >= 0 ? pi + 1 : 0, 0, { id: "subscription", t: "Subscription & Features", ic: "lightbulb" });
  }
  const ni = tabs.findIndex((t) => t.id === "notifications");
  const at = ni >= 0 ? ni : tabs.length;
  const cxTabs = (r === "admin" || r === "super-admin")
    ? [{ sec: "Customer Experience" },
    ...(r === "super-admin" ? [{ id: "customer-mobile", t: "Customer Mobile View", ic: "qr-code" }] : []),
    { id: "customer-experience", t: "Customer Experience", ic: "smartphone" }]
    : [];
  SETTINGS_TABS[r] = [...tabs.slice(0, at), ...COMPLIANCE_SETTINGS_TABS, ...cxTabs, { sec: "Preferences" }, ...tabs.slice(at)];
});
// Costing & margins tab for admin/super-admin (Preferences section).
["admin", "super-admin"].forEach((r) => {
  const tabs = SETTINGS_TABS[r]; if (!tabs || tabs.find((t) => t.id === "costing")) return;
  const ap = tabs.findIndex((t) => t.id === "appearance");
  tabs.splice(ap >= 0 ? ap : tabs.length, 0, { id: "costing", t: "Costing & margins", ic: "calculator" });
});

function SetRow({ k, d, children }) {
  return (
    <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 16, padding: "13px 0", borderBottom: "1px solid var(--gray-100)" }}>
      <div style={{ minWidth: 0 }}><div style={{ fontWeight: 600, fontSize: 13.5 }}>{k}</div><div className="muted" style={{ fontSize: 12.5, marginTop: 1 }}>{d}</div></div>
      <div style={{ flexShrink: 0 }}>{children}</div>
    </div>
  );
}
function SetCard({ title, sub, icon, children, muted }) {
  return (
    <div className="card pad" style={{ marginBottom: 16, ...(muted ? { opacity: 0.6, filter: "grayscale(0.5)", background: "var(--gray-50)" } : {}) }}>
      <div style={{ display: "flex", alignItems: "center", gap: 9, marginBottom: 4 }}>
        {icon && <Icon name={icon} size={16} stroke={2.2} style={{ color: muted ? "var(--gray-400)" : "var(--green-700)" }} />}
        <h3 style={{ fontFamily: "var(--serif)", fontSize: 17, margin: 0 }}>{title}</h3>
      </div>
      {sub && <p className="muted" style={{ fontSize: 13, margin: "0 0 12px" }}>{sub}</p>}
      <div>{children}</div>
    </div>
  );
}

const SETTINGS_TAB_ENTITLEMENT = {
  refids: "reference_ids",
  workflow: "approval_workflows",
  loadbalancing: "approval_workflows",
  bulkimport: "bulk_import",
  recyclebin: "auditability",
  platform: "multi_site",
  "allergen-table": "allergen_table",
  "health-conditions": "regulatory_validation",
  "health-tag-rules": "regulatory_validation",
  "nutrient-rules": "regulatory_validation",
  "ingredient-rules": "regulatory_validation",
  "recipe-table": "compliance_view",
  "compliance-publishing": "publishing_calendar",
  "customer-mobile": "customer_experience",
  "customer-experience": "customer_experience",
  costing: "basic_costing"
};
const SETTINGS_FEATURE_LABEL = {
  approval_workflows: "Publishing Workflow",
  regulatory_validation: "Regulatory Validation",
};
function entitlementsReady() {
  return !window.Entitlements
    || typeof window.Entitlements.ready !== "function"
    || window.Entitlements.ready();
}
function settingsTabLocked(tab) {
  const feature = SETTINGS_TAB_ENTITLEMENT[tab && tab.id];
  return !!(
    feature
    && entitlementsReady()
    && window.Entitlements
    && !window.Entitlements.has(feature)
  );
}
function settingsTabsForRole(role) {
  const source = SETTINGS_TABS[role] || SETTINGS_TABS["media-contributor"];
  // A Super Admin needs to discover every tenant administration surface even
  // when the current subscription does not unlock it. Locked tabs explain the
  // plan requirement; they never mount the protected management component.
  const visible = role === "super-admin" ? source : source.filter((item) => {
    if (item.sec || !SETTINGS_TAB_ENTITLEMENT[item.id]) return true;
    return !!(window.Entitlements && window.Entitlements.has(SETTINGS_TAB_ENTITLEMENT[item.id]));
  });
  return visible.filter((item, index) => {
    if (!item.sec) return true;
    const nextSectionOffset = visible.slice(index + 1).findIndex((next) => !!next.sec);
    const end = nextSectionOffset < 0 ? visible.length : index + 1 + nextSectionOffset;
    return visible.slice(index + 1, end).some((next) => !next.sec);
  });
}
function SettingsFeatureRequired({ feature, onOpenSubscription }) {
  const activePlan = window.Entitlements && window.Entitlements.plan ? window.Entitlements.plan() : "starter";
  const featureLabel = SETTINGS_FEATURE_LABEL[feature] || "This settings feature";
  return (
    <div className="card pad" style={{ maxWidth: 620, margin: "24px auto", textAlign: "center" }}>
      <div className="stat-icon" style={{ margin: "0 auto 14px" }}><Icon name="lock-keyhole" size={24} /></div>
      <h2 style={{ margin: 0 }}>{featureLabel} is not included in {String(activePlan).replace(/^./, (value) => value.toUpperCase())}</h2>
      <p className="muted" style={{ marginTop: 10, lineHeight: 1.55 }}>This administrative area is visible to Super Admins, but it remains unavailable until the workspace subscription includes the required feature.</p>
      <button className="btn primary" style={{ marginTop: 8 }} onClick={onOpenSubscription}><Icon name="lightbulb" size={15} /> View subscription options</button>
    </div>
  );
}

function SettingsScreen() {
  const { role, toast } = useApp();
  const user = currentUser(role);
  const [entitlementRevision, setEntitlementRevision] = React.useState(0);
  React.useEffect(() => {
    const refresh = () => setEntitlementRevision((value) => value + 1);
    window.addEventListener("nutridms-entitlements", refresh);
    return () => window.removeEventListener("nutridms-entitlements", refresh);
  }, []);
  const tabs = settingsTabsForRole(role);
  const [tab, setTab] = React.useState(() => {
    const want = window.__settingsTab; window.__settingsTab = null;
    if (want && tabs.find(t => t.id === want)) return want;
    return tabs[0].id;
  });
  React.useEffect(() => { if (!tabs.find(t => t.id === tab)) setTab(tabs[0].id); }, [role, entitlementRevision]);
  const [collapsed, setCollapsed] = React.useState(() => { try { return localStorage.getItem("nutridms_settings_nav_collapsed") === "1"; } catch (e) { return false; } });
  const toggleCollapse = () => setCollapsed((c) => { const n = !c; try { localStorage.setItem("nutridms_settings_nav_collapsed", n ? "1" : "0"); } catch (e) { } return n; });
  const activeTab = tabs.find((t) => t.id === tab);
  const activeFeature = activeTab && SETTINGS_TAB_ENTITLEMENT[activeTab.id];
  const activeTabLocked = role === "super-admin" && settingsTabLocked(activeTab);

  return (
    <div className={`set-standalone ${collapsed ? "nav-collapsed" : ""} ${tab === "subscription" ? "subscription-mode" : ""}`}>
      <nav className="set-tabs" aria-label="Settings sections">
        <div className="set-tabs-head">
          {!collapsed && <span className="set-tabs-title">Settings</span>}
          <button className="set-tabs-toggle" onClick={toggleCollapse} title={collapsed ? "Expand" : "Collapse"} aria-label={collapsed ? "Expand settings menu" : "Collapse settings menu"}>
            <Icon name={collapsed ? "chevrons-right" : "chevrons-left"} size={16} stroke={2.2} />
          </button>
        </div>
        <div className="set-tabs-scroll">
          {tabs.map((t, i) => t.sec ? (
            collapsed ? <div key={`sec-${i}`} className="set-tab-sec-divider" /> :
              <div key={`sec-${i}`} className="set-tab-sec">{t.sec}{t.secHint && <span className="set-tab-sec-hint">{t.secHint}</span>}</div>
          ) : (
            <button key={t.id} className={`set-tab ${tab === t.id ? "on" : ""} ${t.step ? "stepped" : ""} ${role === "super-admin" && settingsTabLocked(t) ? "locked" : ""}`} onClick={() => setTab(t.id)} title={collapsed ? `${t.t}${role === "super-admin" && settingsTabLocked(t) ? " (locked)" : ""}` : undefined}>
              {t.step ? <span className="set-tab-step">{t.step}</span> : <Icon name={t.ic} size={15} stroke={2.2} />} <span className="set-tab-label">{t.t}</span>{role === "super-admin" && settingsTabLocked(t) && <Icon name="lock-keyhole" size={13} stroke={2.2} style={{ marginLeft: "auto" }} />}
            </button>
          ))}
        </div>
      </nav>
      <div className="set-pane">
        <Crumbs path={[{ label: "Settings" }]} />
        <div className="page-head">
          <div>
            <h1 className="page-title">Settings</h1>
            <p className="page-sub">{ROLES[role] ? ROLES[role].label : "Account"} preferences, tailored to what you do in NutriDMS.</p>
          </div>
        </div>
        <div className="set-content">
          {activeTabLocked ? <SettingsFeatureRequired feature={activeFeature} onOpenSubscription={() => setTab("subscription")} /> : <>
          {tab === "profile" && <SetProfile user={user} role={role} toast={toast} />}
          {tab === "subscription" && <SetSubscription role={role} toast={toast} />}
          {tab === "submission" && <SetSubmission toast={toast} />}
          {tab === "review" && <SetReview toast={toast} />}
          {tab === "compliance" && <SetCompliance toast={toast} />}
          {tab === "editorial" && <SetEditorial toast={toast} />}
          {tab === "organization" && <SetOrganization toast={toast} role={role} />}
          {tab === "refids" && <SetReferenceIds toast={toast} role={role} />}
          {tab === "workflow" && <SetPublishingWorkflow toast={toast} />}
          {tab === "loadbalancing" && <SetLoadBalancing toast={toast} role={role} />}
          {tab === "costing" && <SetCosting toast={toast} role={role} />}
          {tab === "bulkimport" && <BulkImportScreen embedded />}
          {tab === "recyclebin" && <SetRecycleBin role={role} toast={toast} />}
          {tab === "access" && <SetAccess toast={toast} />}
          {tab === "platform" && <SetPlatform toast={toast} />}
          {tab === "loraa-messenger" && <SetLoraaMessenger role={role} toast={toast} />}
          {tab === "notifications" && <SetNotifications role={role} toast={toast} />}
          {tab === "appearance" && <SetAppearance toast={toast} role={role} />}
          {tab === "customer-mobile" && (window.CustomerMobileView ? React.createElement(window.CustomerMobileView) : null)}
          {tab === "security" && <SetSecurity toast={toast} />}
          {tab === "nutrient-rules" && <NutrientRulesPage embedded />}
          {tab === "ingredient-rules" && <IngredientRulesPage embedded />}
          {tab === "health-tag-rules" && <HealthTagRulesPage embedded />}
          {tab === "health-conditions" && <HealthConditionsPage embedded />}
          {tab === "recipe-table" && <ComplianceRecipeTablePage embedded />}
          {tab === "allergen-table" && <AllergenTablePage embedded />}
          {tab === "compliance-publishing" && <SetCompliancePublishing toast={toast} role={role} />}
          {tab === "customer-experience" && <SetCustomerExperience toast={toast} role={role} />}
          </>}
        </div>
      </div>
    </div>
  );
}

function SetProfile({ user, role, toast }) {
  const AVATAR_COLORS = ["#2f7d32", "#ea8a1e", "#2a74e0", "#2b3b78", "#7b3ff2", "#c936c9", "#b5730c", "#e24a24", "#127a52"];
  const TIME_ZONES = (() => {
    const fallback = ["UTC", "Africa/Lagos", "America/Los_Angeles", "America/New_York", "Asia/Tokyo", "Australia/Sydney", "Europe/London"];
    try {
      return typeof Intl.supportedValuesOf === "function"
        ? Intl.supportedValuesOf("timeZone")
        : fallback;
    } catch (e) { return fallback; }
  })();
  const profileValue = () => {
    try {
      return window.NutriIdentityAssets && window.NutriIdentityAssets.profile
        ? window.NutriIdentityAssets.profile()
        : {
          photoDataUrl: (window.__nutridmsAuthenticatedUser || {}).profilePictureUrl || (window.__nutridmsAuthenticatedUser || {}).profile_picture_url || "",
          color: AVATAR_COLORS[0]
        };
    } catch (e) { return { photoDataUrl: "", color: AVATAR_COLORS[0] }; }
  };
  const initialProfile = profileValue();
  const [color, setColor] = React.useState(initialProfile.color || AVATAR_COLORS[0]);
  const [photo, setPhoto] = React.useState(initialProfile.photoDataUrl || "");
  const [photoFile, setPhotoFile] = React.useState(null);
  const [photoDirty, setPhotoDirty] = React.useState(false);
  const [displayName, setDisplayName] = React.useState(user.name || "");
  const [timeZone, setTimeZone] = React.useState(() => {
    try { return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC"; }
    catch (e) { return "UTC"; }
  });
  const [picker, setPicker] = React.useState(false);
  const [roles, setRoles] = React.useState([]);
  const [selectedRoleId, setSelectedRoleId] = React.useState("");
  const [membership, setMembership] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [saving, setSaving] = React.useState(false);
  const fileRef = React.useRef(null);
  const canEditRole = role === "super-admin";
  const label = user.roleLabel || (ROLES[role] ? ROLES[role].label : role);
  const email = user.email || "";

  React.useEffect(() => {
    const sync = () => {
      const profile = profileValue();
      setColor(profile.color || AVATAR_COLORS[0]);
      setPhoto(profile.photoDataUrl || "");
    };
    window.addEventListener("nutridms-profile", sync);
    return () => window.removeEventListener("nutridms-profile", sync);
  }, []);

  React.useEffect(() => {
    let cancelled = false;
    const loadProfile = async () => {
      if (!window.NutriAPI) {
        if (!cancelled) setLoading(false);
        return;
      }
      setLoading(true);
      try {
        const saved = await window.NutriAPI.get("/users/profile/");
        if (cancelled || !saved) return;
        const persistedPhoto = saved.profile_picture || "";
        setDisplayName(saved.display_name || user.name || "");
        setTimeZone(saved.timezone || "UTC");
        setPhoto(persistedPhoto);
        setPhotoFile(null);
        setPhotoDirty(false);
        if (window.NutriIdentityAssets && typeof window.NutriIdentityAssets.adoptProfilePicture === "function") {
          window.NutriIdentityAssets.adoptProfilePicture(persistedPhoto, initialProfile.color || AVATAR_COLORS[0]);
        }
      } catch (e) {
        if (!cancelled) toast((e && e.message) || "Profile settings could not be loaded.");
      } finally {
        if (!cancelled) setLoading(false);
      }
    };
    loadProfile();
    return () => { cancelled = true; };
  }, [user.id]);

  React.useEffect(() => {
    if (!picker) return;
    const close = (e) => { if (!e.target.closest(".pf-avatar-wrap")) setPicker(false); };
    document.addEventListener("click", close);
    return () => document.removeEventListener("click", close);
  }, [picker]);

  const pickColor = (c) => {
    setPicker(false);
    setColor(c);
    setPhoto("");
    setPhotoFile(null);
    setPhotoDirty(true);
  };
  const onFile = (e) => {
    const f = e.target.files && e.target.files[0]; if (!f) return;
    if (!/^image\/(png|jpeg|webp)$/.test(f.type)) { toast("Use a PNG, JPG, or WebP file."); return; }
    if (f.size > 1024 * 1024) { toast("Profile photo must be under 1 MB."); return; }
    const r = new FileReader();
    r.onload = () => {
      setPicker(false);
      setPhotoFile(f);
      setPhoto(String(r.result || ""));
      setPhotoDirty(true);
    };
    r.readAsDataURL(f);
  };

  React.useEffect(() => {
    let cancelled = false;
    if (!canEditRole) return undefined;
    const loadRoleOptions = async () => {
      if (!window.NutriAuth || !window.NutriSettings || typeof window.NutriSettings.organizationRoles !== "function") return;
      try {
        const me = await window.NutriAuth.me();
        const memberships = Array.isArray(me && me.memberships) ? me.memberships : [];
        const activeOrganizationId = String(
          (me && me.active_organization_id) || (window.NutriAPI && window.NutriAPI.tokens && window.NutriAPI.tokens.org) || ""
        );
        const activeMembership = memberships.find((item) => String(item && item.organization && item.organization.id || "") === activeOrganizationId) || memberships[0];
        const roleId = activeMembership && activeMembership.role && activeMembership.role.id;
        if (!activeMembership || !activeMembership.id || !roleId) throw new Error("Your active membership could not be found.");
        const rolePayload = await window.NutriSettings.organizationRoles();
        const availableRoles = Array.isArray(rolePayload) ? rolePayload : (rolePayload && rolePayload.results) || [];
        if (cancelled) return;
        setMembership({ id: activeMembership.id, roleId: String(roleId) });
        setSelectedRoleId(String(roleId));
        setRoles(availableRoles.filter((item) => item && item.id && item.label));
      } catch (error) {
        if (!cancelled) toast((error && error.message) || "Role options could not be loaded.");
      }
    };
    loadRoleOptions();
    return () => { cancelled = true; };
  }, [canEditRole]);

  const updateDisplayedUser = (name, profilePicture) => {
    const normalizedName = String(name || user.name || email).trim();
    const parts = normalizedName.split(/\s+/).filter(Boolean);
    const initials = (parts.length > 1 ? parts[0][0] + parts[parts.length - 1][0] : normalizedName.slice(0, 2)).toUpperCase();
    const next = { ...(window.__nutridmsAuthenticatedUser || user), name: normalizedName, initials, profile_picture_url: profilePicture };
    window.__nutridmsAuthenticatedUser = next;
    window.dispatchEvent(new CustomEvent("nutridms-user", { detail: next }));
    window.dispatchEvent(new CustomEvent("nutridms-backend", { detail: { connected: true } }));
  };

  const syncAvatar = (photoDataUrl) => {
    if (window.NutriIdentityAssets && typeof window.NutriIdentityAssets.adoptProfilePicture === "function") {
      window.NutriIdentityAssets.adoptProfilePicture(String(photoDataUrl || ""), color || AVATAR_COLORS[0]);
    }
    if (window.parent && window.parent !== window) {
      window.parent.postMessage({
        type: "nutridms-profile-updated",
        userId: user.id,
        profilePictureUrl: String(photoDataUrl || "")
      }, window.location.origin);
    }
  };

  const saveProfile = async () => {
    const name = displayName.trim();
    if (!name) { toast("Display name is required."); return; }
    if (!window.NutriAPI) {
      toast("Your profile API is not available."); return;
    }
    setSaving(true);
    try {
      const payload = { display_name: name, timezone: timeZone || "UTC" };
      let saved;
      if (photoFile) {
        const form = new FormData();
        form.append("display_name", name);
        form.append("timezone", timeZone || "UTC");
        form.append("profile_picture", photoFile);
        saved = await window.NutriAPI.patch("/users/profile/", form);
      } else {
        if (photoDirty) payload.profile_picture_data_url = "";
        saved = await window.NutriAPI.patch("/users/profile/", payload);
      }
      const savedPhoto = typeof (saved && saved.profile_picture) === "string" ? saved.profile_picture : photo;
      const pictureChanged = photoDirty;
      setDisplayName(String(saved && saved.display_name || name));
      setPhoto(savedPhoto || "");
      setPhotoFile(null);
      setPhotoDirty(false);
      updateDisplayedUser(saved && saved.display_name || name, savedPhoto || "");
      syncAvatar(savedPhoto);

      const roleChanged = canEditRole && membership && selectedRoleId && selectedRoleId !== membership.roleId;
      if (roleChanged) {
        if (typeof window.NutriSettings.updateMemberRole !== "function") throw new Error("Role updates are not available.");
        await window.NutriSettings.updateMemberRole(membership.id, selectedRoleId);
        toast("Profile and role saved. Refreshing access…");
        window.setTimeout(() => window.location.reload(), 400);
        return;
      }
      toast(pictureChanged ? "Profile photo synced across your devices." : "Profile saved.");
    } catch (error) {
      toast((error && error.message) || "Profile could not be saved.");
    } finally {
      setSaving(false);
    }
  };

  return (
    <SetCard title="Profile" sub="Your identity inside your workspace" icon="user">
      <div className="pf">
        <div className="pf-avatar-wrap">
          <button className="pf-avatar" style={photo ? { backgroundImage: `url("${photo}")` } : { background: color }} onClick={() => setPicker((p) => !p)} aria-label="Change profile picture">
            {!photo && <span className="pf-avatar-init">{user.initials}</span>}
            <span className="pf-avatar-hover"><Icon name="camera" size={20} /> Edit</span>
          </button>
          <button className="pf-cam" onClick={() => setPicker((p) => !p)} aria-label="Edit avatar"><Icon name="camera" size={15} /></button>
          {picker && (
            <div className="pf-picker" onClick={(e) => e.stopPropagation()}>
              <div className="pf-picker-t">Select Profile Color</div>
              <div className="pf-swatches">
                {AVATAR_COLORS.map((c) => (
                  <button key={c} className={`pf-swatch ${!photo && color === c ? "on" : ""}`} style={{ background: c }} onClick={() => pickColor(c)} aria-label={c}>
                    {!photo && color === c && <Icon name="check" size={13} stroke={3} />}
                  </button>
                ))}
              </div>
              <button className="pf-upload" disabled={saving} onClick={() => fileRef.current && fileRef.current.click()}>Or upload Profile Photo <Icon name="arrow-right" size={13} /></button>
              <input ref={fileRef} type="file" accept="image/png,image/jpeg,image/webp" hidden onChange={onFile} />
            </div>
          )}
        </div>
        <div className="pf-name">{displayName || user.name}</div>
        <div className="pf-role">{label}</div>
      </div>

      <div className="pf-grid">
        <div className="field"><label>Display Name</label><input className="input" value={displayName} disabled={loading || saving} onChange={(e) => setDisplayName(e.target.value)} /></div>
        <div className="field"><label>Email Address</label><input className="input" value={email} disabled /></div>
        <div className="field"><label>Role</label>
          {canEditRole ? (
            <select className="select" value={selectedRoleId} disabled={!membership || saving} onChange={(e) => setSelectedRoleId(e.target.value)}>
              {!selectedRoleId && <option value="">Loading roles…</option>}
              {roles.map((item) => <option key={item.id} value={item.id}>{item.label}</option>)}
            </select>
          ) : <div className="input" aria-readonly="true">{label}</div>}
        </div>
        <div className="field"><label>Time Zone</label>
          <select className="select" value={timeZone} onChange={(e) => setTimeZone(e.target.value)} disabled={loading || saving}>
            {!TIME_ZONES.includes(timeZone) && <option value={timeZone}>{timeZone}</option>}
            {TIME_ZONES.map((z) => <option key={z} value={z}>{z}</option>)}
          </select>
        </div>
      </div>
      <div className="pf-foot">
        <button className="btn primary" disabled={loading || saving} onClick={saveProfile}><Icon name={saving ? "loader-circle" : "check-circle-2"} size={16} /> {saving ? "Saving…" : "Save Changes"}</button>
      </div>
    </SetCard>
  );
}

function SetSubscription({ role, toast }) {
  const [, bump] = React.useState(0);
  React.useEffect(() => {
    const h = () => bump(n => n + 1);
    window.addEventListener("nutridms-mealprograms", h);
    window.addEventListener("nutridms-digital-signage", h);
    return () => {
      window.removeEventListener("nutridms-mealprograms", h);
      window.removeEventListener("nutridms-digital-signage", h);
    };
  }, []);
  const canConfig = ["admin", "super-admin"].includes(role);
  const [plan, setPlan] = React.useState("loading");
  const enabled = (typeof mpEnabled === "function") ? mpEnabled() : false;
  const isPro = plan === "professional";
  const isStarter = plan === "starter";
  const [cycle, setCycle] = React.useState("monthly");     // monthly | annual
  const [billingBusy, setBillingBusy] = React.useState(false);
  const [catalogPlans, setCatalogPlans] = React.useState([]);
  const [catalogLoading, setCatalogLoading] = React.useState(true);
  const [catalogError, setCatalogError] = React.useState("");

  React.useEffect(() => {
    let cancelled = false;
    if (!window.NutriAuth) return () => { cancelled = true; };
    window.NutriAuth.me().then((payload) => {
      if (cancelled) return;
      const activeId = String((payload && payload.active_organization_id) || "");
      const membership = ((payload && payload.memberships) || []).find((item) => String(item.organization && item.organization.id) === activeId)
        || ((payload && payload.memberships) || [])[0];
      const organization = membership && membership.organization;
      setPlan(String((organization && organization.subscription_tier) || "starter"));
      setCycle((organization && organization.billing_cycle) === "annual" ? "annual" : "monthly");
    }).catch((error) => {
      setPlan("starter");
      toast && toast((error && error.message) || "Subscription status could not be loaded.");
    });
    return () => { cancelled = true; };
  }, []);

  React.useEffect(() => {
    let cancelled = false;
    const loadPlanCatalog = async () => {
      if (!window.NutriAPI) {
        if (!cancelled) {
          setCatalogError("The NutriDMS billing catalogue is not available.");
          setCatalogLoading(false);
        }
        return;
      }
      try {
        const response = await window.NutriAPI.get("/billing/plans");
        const rows = Array.isArray(response && response.plans) ? response.plans : [];
        if (!cancelled) setCatalogPlans(rows);
      } catch (error) {
        if (!cancelled) setCatalogError((error && error.message) || "Unable to load the billing catalogue.");
      } finally {
        if (!cancelled) setCatalogLoading(false);
      }
    };
    loadPlanCatalog();
    return () => { cancelled = true; };
  }, []);

  const selectedInterval = cycle === "annual" ? "year" : "month";
  const intervalForPlan = (row) => {
    const interval = String(row && row.billing_interval || "").toLowerCase();
    if (interval === "month" || interval === "year") return interval;
    const billingType = String(row && row.billing_type || "").toLowerCase();
    return billingType === "annual" || billingType === "yearly" || billingType === "year"
      ? "year"
      : "month";
  };
  const featuresForPlan = (row) => {
    const assignments = Array.isArray(row && row.plan_features)
      ? row.plan_features
      : (Array.isArray(row && row.features) ? row.features : []);
    return assignments
      .filter((assignment) => assignment && assignment.is_active !== false)
      .map((assignment) => assignment.feature || assignment)
      .filter((feature) => feature && feature.display !== false)
      .map((feature) => String(feature.name || feature.label || feature.code || "").trim())
      .filter(Boolean);
  };
  const allPlans = catalogPlans.map((row) => {
    return {
      id: row.id,
      key: String(row.key || row.id),
      label: row.name,
      price: row.price == null ? null : Number(row.price),
      currency: row.currency || "usd",
      interval: intervalForPlan(row),
      tagline: row.description || "A NutriDMS plan configured for your organization.",
      features: featuresForPlan(row),
      seatLimit: Number(row.seat_limit) || 1,
      popular: String(row.key) === "professional",
    };
  });
  const PLANS = allPlans.filter((item) => item.interval === selectedInterval);
  const PLAN_ORDER = ["starter", "growth", "professional", "enterprise"];
  const isCapacityFeature = (feature) => /\b(staff|users?|locations?|credits?)\b/i.test(feature);
  const MATRIX = Array.from(new Set(PLANS.flatMap((item) => item.features))).map((feature) => {
    const exact = new Set(PLANS.filter((item) => item.features.includes(feature)).map((item) => item.key));
    const introducedAt = Math.min(...Array.from(exact).map((key) => PLAN_ORDER.indexOf(key)).filter((index) => index >= 0));
    const included = isCapacityFeature(feature)
      ? exact
      : new Set(PLANS.filter((item) => PLAN_ORDER.indexOf(item.key) >= introducedAt).map((item) => item.key));
    return { feature, included, introducedAt, capacity: isCapacityFeature(feature) };
  }).sort((left, right) => (
    left.introducedAt - right.introducedAt
    || Number(left.capacity) - Number(right.capacity)
    || left.feature.localeCompare(right.feature)
  ));
  const cell = (v) => v === true ? <Icon name="check" size={15} stroke={2.6} style={{ color: "var(--green-600)" }} />
    : v === false ? <Icon name="minus" size={15} style={{ color: "var(--gray-300)" }} />
      : <span style={{ fontSize: 11, fontWeight: 700, color: "var(--gray-500)" }}>{v}</span>;
  const price = (p) => {
    if (!p || !Number.isFinite(p.price)) return "Custom";
    try {
      return new Intl.NumberFormat("en-US", { style: "currency", currency: String(p.currency || "usd").toUpperCase(), maximumFractionDigits: p.price % 1 ? 2 : 0 }).format(p.price);
    } catch (_) {
      return "$" + p.price.toLocaleString();
    }
  };
  const ctaLabel = (p) => p.key === plan ? "Current plan" : !canConfig ? "Admin sign-in required" : "Change plan";

  const activePlan = PLANS.find((item) => item.key === plan) || allPlans.find((item) => item.key === plan) || null;
  const includedUsers = activePlan ? activePlan.seatLimit : ({ starter: 5, growth: 12, professional: 25, enterprise: 100 }[plan] || 25);
  const includedTokens = ({ starter: 500000, growth: 2500000, professional: 5000000, enterprise: 25000000 }[plan] || 5000000);
  const jumpToSection = (id) => {
    const target = document.getElementById(id);
    if (target) target.scrollIntoView({ behavior: "smooth", block: "start" });
  };
  const openBillingPortal = async () => {
    if (!canConfig) { toast("Only an organization Admin can manage billing."); return; }
    setBillingBusy(true);
    try {
      const result = await window.NutriAPI.post("/billing/portal", {
        locale: (document.documentElement.lang || "en").split("-")[0],
      });
      if (!result || !result.portal_url) throw new Error("Stripe billing is not available for this workspace yet.");
      window.top.location.assign(result.portal_url);
    } catch (error) {
      toast((error && error.message) || "Stripe billing could not be opened.");
    } finally {
      setBillingBusy(false);
    }
  };

  const activatePlan = async (selectedPlan) => {
    if (!canConfig) { toast("Sign in with an organization Admin account to change the plan"); return false; }
    try {
      const result = await window.NutriAPI.post("/billing/change-plan", {
        plan: String(selectedPlan.id),
        billing_cycle: cycle,
        locale: (document.documentElement.lang || "en").split("-")[0],
      });
      if (result && result.checkout_url) {
        window.top.location.assign(result.checkout_url);
        return true;
      }
      const effective = String((result && result.plan) || selectedPlan.key).toLowerCase();
      setPlan(effective);
      if (window.Entitlements && typeof window.Entitlements.syncBillingUsage === "function") {
        await window.Entitlements.syncBillingUsage();
      }
      window.dispatchEvent(new Event("nutridms-entitlements"));
      window.dispatchEvent(new Event("nutridms-nav"));
      toast((allPlans.find((item) => item.key === effective) || selectedPlan).label + " plan is now active.");
      return true;
    } catch (error) {
      toast((error && error.message) ? "Plan change failed: " + error.message : "Plan change failed. Please try again.");
      return false;
    }
  };

  const choose = (selectedPlan) => {
    if (!canConfig) { toast("Sign in with an organization Admin account to change the plan"); return; }
    if (selectedPlan.key === plan) return;
    activatePlan(selectedPlan);
  };

  return (
    <div className="subscription-saas-page">
      <section className="sub-command-hero" aria-labelledby="subscription-page-title">
        <div className="sub-command-copy">
          <span className="sub-command-eyebrow"><Icon name="shield-check" size={14} /> Workspace billing</span>
          <h2 id="subscription-page-title">Subscription &amp; Features</h2>
          <p>Control your plan, seats, Loraa AI usage, add-ons and feature access from one secure billing workspace.</p>
          <div className="sub-command-status">
            <span><i /> Active subscription</span>
            <span><Icon name="building-2" size={13} /> Organization managed</span>
            <span><Icon name="lock-keyhole" size={13} /> Stripe secured</span>
          </div>
          <div className="sub-command-actions">
            <button className="btn sub-hero-secondary" onClick={() => jumpToSection("subscription-plans")}><Icon name="layout-grid" size={15} /> Compare plans</button>
            <button className="btn sub-hero-primary" onClick={openBillingPortal} disabled={billingBusy || !canConfig}><Icon name="credit-card" size={15} /> {billingBusy ? "Opening Stripe…" : "Manage billing"}</button>
          </div>
        </div>
        <div className="sub-command-summary">
          <div className="sub-command-plan">
            <span>Current plan</span>
            <strong>{activePlan ? activePlan.label : (catalogLoading ? "Loading…" : plan)}</strong>
            <small>{activePlan ? `${price(activePlan)} / ${activePlan.interval === "year" ? "year" : "month"}` : "Live billing catalogue"}</small>
          </div>
          <div className="sub-command-mini-grid">
            <div><span>Seats included</span><strong>{includedUsers}</strong></div>
            <div><span>AI tokens</span><strong>{includedTokens.toLocaleString()}</strong></div>
            <div><span>Billing view</span><strong>{cycle === "annual" ? "Yearly" : "Monthly"}</strong></div>
            <div><span>Access</span><strong>{canConfig ? "Admin" : "View only"}</strong></div>
          </div>
        </div>
      </section>

      <nav className="sub-page-nav" aria-label="Subscription page sections">
        <button onClick={() => jumpToSection("subscription-overview")}><Icon name="gauge" size={14} /> Overview</button>
        <button onClick={() => jumpToSection("subscription-plans")}><Icon name="layers-3" size={14} /> Plans</button>
        <button onClick={() => jumpToSection("subscription-ai")}><Icon name="sparkles" size={14} /> Loraa AI</button>
        <button onClick={() => jumpToSection("subscription-addons")}><Icon name="blocks" size={14} /> Add-ons</button>
        <button onClick={() => jumpToSection("subscription-comparison")}><Icon name="table-2" size={14} /> Compare</button>
      </nav>

      <section id="subscription-overview" className="subscription-overview subscription-scroll-target">
        <div className="sub-section-heading">
          <div><span>Account overview</span><h3>Your workspace at a glance</h3></div>
          <p>Live entitlements from your NutriDMS organization and Stripe billing profile.</p>
        </div>
        <StaffSeatsCard plan={plan} canConfig={canConfig} toast={toast} />
      </section>

      <section id="subscription-plans" className="subscription-plans-section subscription-scroll-target">
        <div className="sub-section-heading">
          <div><span>Plans</span><h3>Choose the right operating tier</h3></div>
          <p>Every plan keeps the NutriDMS green identity while scaling governance, automation and AI capacity.</p>
        </div>
        <SetCard title="Plan" sub="Your NutriDMS subscription determines which modules are available." icon="lightbulb">
          <div className="sub-cycle">
            <button className={cycle === "monthly" ? "on" : ""} onClick={() => setCycle("monthly")}>Monthly</button>
            <button className={cycle === "annual" ? "on" : ""} onClick={() => setCycle("annual")}>Yearly</button>
          </div>
          <div className="sub-plans">
            {catalogLoading && <p className="muted">Loading {cycle === "annual" ? "yearly" : "monthly"} plans…</p>}
            {!catalogLoading && !PLANS.length && <p className="muted">{catalogError || `No ${cycle === "annual" ? "yearly" : "monthly"} plans are currently available.`}</p>}
            {PLANS.map((p) => (
              <div key={p.id} className={`sub-plan ${plan === p.key ? "current" : ""} ${p.popular ? "popular" : ""}`}>
                {p.popular && <span className="sub-plan-pop">Most popular</span>}
                <div className="sub-plan-head">
                  <span className="sub-plan-name">{p.label}</span>
                  {plan === p.key && <span className="pill success" style={{ fontSize: 10 }}>Current</span>}
                </div>
                <div className="sub-plan-price">
                  <><span className="sub-plan-amt">{price(p)}</span><span className="sub-plan-per">/{p.interval === "year" ? "year" : "month"}</span></>
                </div>
                <p className="sub-plan-tag">{p.tagline}</p>
                <ul className="sub-plan-feats">
                  {p.features.map((f) => <li key={f}><Icon name="check" size={13} stroke={2.6} /> {f}</li>)}
                </ul>
                <button className={`btn ${plan === p.key ? "secondary" : "primary"} sub-plan-cta`} disabled={plan === p.key} onClick={() => choose(p)}>
                  <>{plan !== p.key && <Icon name="arrow-up-circle" size={14} />}{ctaLabel(p)}</>
                </button>
              </div>
            ))}
          </div>
          <div style={{ marginTop: 14, padding: "12px 14px", border: "1px solid var(--green-200)", borderRadius: 12, background: "var(--green-50)", display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, flexWrap: "wrap" }}>
            <div>
              <strong style={{ fontSize: 13 }}>Live catalogue and feature access</strong>
              <div className="muted" style={{ fontSize: 12, marginTop: 3 }}>
                Prices, billing intervals and the features shown above come from the NutriDMS billing catalogue. Access follows the subscription tier assigned to your organization.
              </div>
            </div>
            {activePlan && <span className="pill success"><Icon name="check" size={12} /> {activePlan.label} active</span>}
          </div>
          {!canConfig && <p className="muted" style={{ fontSize: 12, marginTop: 12 }}><Icon name="lock" size={12} /> You can compare every tier. Sign in with an organization Admin account to change the subscription.</p>}
        </SetCard>
      </section>

      <section id="subscription-ai" className="subscription-ai-section subscription-scroll-target">
        <div className="sub-section-heading">
          <div><span>Usage &amp; billing</span><h3>Loraa AI control center</h3></div>
          <p>Provider-reported token accounting, request-level logs, alerts and secure top-ups.</p>
        </div>
        <LoraaCreditsCard canConfig={canConfig} toast={toast} />
      </section>

      <section id="subscription-addons" className="subscription-addons subscription-scroll-target">
        <div className="sub-section-heading subscription-grid-heading">
          <div><span>Feature controls</span><h3>Add-ons &amp; workspace modules</h3></div>
          <p>Activate only the capabilities your organization needs. Existing data is preserved when a module is paused.</p>
        </div>

        <LiveBillingAddonCard
          addOn="digital_signage"
          title="Digital Signage Studio"
          sub="Design, schedule, publish and monitor restaurant and hospitality screens from inside NutriDMS."
          icon="monitor-play"
          plan={plan}
          canConfig={canConfig}
          toast={toast}
          openLabel="Open Digital Signage"
        />
        <SetCard title="Meal Programs" sub="Create, review, approve, and distribute enterprise meal programs." icon="utensils">
          {isStarter || plan === "growth" ? (
            <div className="pill warning" style={{ padding: "11px 14px", fontSize: 13 }}><Icon name="arrow-up-circle" size={14} /> Upgrade to Professional or Enterprise to unlock Meal Programs.</div>
          ) : (
            <>
              <SetRow k="Enable Meal Programs" d={plan === "enterprise" ? "Included in Enterprise, always on." : "Show the module in the sidebar and restore all permissions, dashboards, and reports."}>
                {plan === "enterprise" ? <span className="pill success"><Icon name="check" size={12} /> Enabled</span>
                  : <Switch defaultOn={enabled} onChange={(v) => canConfig ? mpSetEnabled(v) : toast("Only an Admin can change this")} />}
              </SetRow>
              <div style={{ display: "flex", gap: 20, flexWrap: "wrap", marginTop: 12, fontSize: 12.5, color: "var(--gray-600)" }}>
                <div><strong style={{ color: "var(--gray-800)" }}>When ON</strong><div style={{ marginTop: 4 }}>Sidebar nav, permissions, dashboards & reports restored.</div></div>
                <div><strong style={{ color: "var(--gray-800)" }}>When OFF</strong><div style={{ marginTop: 4 }}>Module hidden everywhere, all historical data is preserved.</div></div>
              </div>
              {isPro && !canConfig && enabled && <p className="muted" style={{ fontSize: 12, marginTop: 10 }}>Module is currently enabled for this workspace.</p>}
            </>
          )}
        </SetCard>

        <SetCard title="NutriDMS Restaurant Portal" sub="Optional add-on subscription, restaurant & food-service intelligence, kept separate from your core compliance platform." icon="utensils-crossed" muted={true}>
          <SetRow k="Enable Restaurant Portal" d="Adds the Reporting & Restaurant Intelligence suite (Sales, Nutrition, Customer, Menu, Health, Revenue, Campaign, Forecasting, Benchmarking, AI Advisor, Executive Dashboard, Customer Experience & Exports) as its own sidebar section. Off by default so it never clutters core NutriDMS.">
            <span className="pill neutral" style={{ marginRight: 10, fontSize: 11 }}><Icon name="lock" size={11} stroke={2.2} /> Coming soon</span>
            <Switch defaultOn={false} disabled={true} onChange={() => toast("The Restaurant Portal is reserved for a future release and cannot be enabled yet.")} />
          </SetRow>
          <div style={{ display: "flex", gap: 20, flexWrap: "wrap", marginTop: 12, fontSize: 12.5, color: "var(--gray-600)" }}>
            <div><strong style={{ color: "var(--gray-800)" }}>When ON</strong><div style={{ marginTop: 4 }}>A “Restaurant Portal” section appears with the full restaurant analytics suite (tier-gated within).</div></div>
            <div><strong style={{ color: "var(--gray-800)" }}>When OFF</strong><div style={{ marginTop: 4 }}>Hidden entirely, core ingredients, recipes, compliance, GS1 &amp; product specs are unaffected. Data is preserved.</div></div>
          </div>
          <p className="muted" style={{ fontSize: 12, marginTop: 10 }}><Icon name="info" size={12} /> Offerings and Meal Programs remain part of core NutriDMS and are unaffected by this toggle.</p>
          <p className="muted" style={{ fontSize: 12, marginTop: 6 }}><Icon name="lock" size={12} /> Reserved for a future build. This module is disabled for everyone, including Super Admins, and cannot be turned on.</p>
        </SetCard>

        <SetCard title="Offerings & Compliance" sub="Show the Offerings workspace and the Compliance dashboard in the sidebar. One switch controls both." icon="boxes">
          <SetRow k="Show Offerings & Compliance in sidebar" d="Adds the Offerings group (Products, Menu Items, Combo Meals, Meal Plans, Catering) and the Compliance dashboard to the navigation.">
            <Switch defaultOn={(typeof offeringsComplianceEnabled === "function") ? offeringsComplianceEnabled() : true} disabled={!canConfig} onChange={(v) => canConfig ? offeringsComplianceSetEnabled(v) : toast("Only an Admin can change this")} />
          </SetRow>
          <div style={{ display: "flex", gap: 20, flexWrap: "wrap", marginTop: 12, fontSize: 12.5, color: "var(--gray-600)" }}>
            <div><strong style={{ color: "var(--gray-800)" }}>When ON</strong><div style={{ marginTop: 4 }}>Offerings and the Compliance dashboard both appear in the sidebar.</div></div>
            <div><strong style={{ color: "var(--gray-800)" }}>When OFF</strong><div style={{ marginTop: 4 }}>Both are hidden from the sidebar, all offerings and compliance data are preserved.</div></div>
          </div>
          {!canConfig && <p className="muted" style={{ fontSize: 12, marginTop: 10 }}><Icon name="lock" size={12} /> Only an Admin can change module availability.</p>}
        </SetCard>

        <SetCard title="Master Ingredient Library" sub="Fetch verified, dietitian-reviewed ingredient data (USDA, FDA, CFIA, Health Canada) straight into the Add Ingredient form. Included on every paid plan." icon="database">
          <SetRow k="Enable Loraa Master Fetch" d="Included on your plan. Shows the Master Fetch button on Add Ingredient for users who can create ingredients.">
            <Switch defaultOn={window.MasterIngredients ? window.MasterIngredients.settings().enabled : true} disabled={!canConfig} onChange={(v) => { if (!canConfig) return toast("Only an Admin can change this"); if (window.MasterIngredients) window.MasterIngredients.setSettings({ enabled: v }); toast(v ? "Master Fetch enabled" : "Master Fetch disabled"); }} />
          </SetRow>
          <SetRow k="Allow editing imported values" d="Let users edit verified fields after importing. Any change is recorded in the audit log.">
            <Switch defaultOn={window.MasterIngredients ? window.MasterIngredients.settings().allow_override : true} disabled={!canConfig} onChange={(v) => { if (canConfig && window.MasterIngredients) window.MasterIngredients.setSettings({ allow_override: v }); }} />
          </SetRow>
          <SetRow k="Require reason for overrides" d="Ask for a reason when a user changes a verified value.">
            <Switch defaultOn={window.MasterIngredients ? window.MasterIngredients.settings().require_override_reason : true} disabled={!canConfig} onChange={(v) => { if (canConfig && window.MasterIngredients) window.MasterIngredients.setSettings({ require_override_reason: v }); }} />
          </SetRow>
        </SetCard>

        <LiveBillingAddonCard
          addOn="customer_experience"
          title="Customer Experience add-on"
          sub="Public customer pages for QR menus, nutrition and allergens, loyalty, reviews and CRM."
          icon="smartphone"
          plan={plan}
          canConfig={canConfig}
          toast={toast}
        />
        <SetCard title="Label Studio" sub="Generate Health Canada–aligned Nutrition Facts tables with a live preview." icon="tag">
          <SetRow k="Show Label Studio in sidebar" d="Adds Label Studio to the navigation so reviewers can generate, screen, approve, lock and export Canadian NFt labels.">
            <Switch defaultOn={(typeof labelStudioEnabled === "function") ? labelStudioEnabled() : true} onChange={(v) => canConfig ? labelStudioSetEnabled(v) : toast("Only an Admin can change this")} />
          </SetRow>
          <div style={{ display: "flex", gap: 20, flexWrap: "wrap", marginTop: 12, fontSize: 12.5, color: "var(--gray-600)" }}>
            <div><strong style={{ color: "var(--gray-800)" }}>When ON</strong><div style={{ marginTop: 4 }}>Label Studio appears in the sidebar with live NFt preview, FOP screening &amp; export.</div></div>
            <div><strong style={{ color: "var(--gray-800)" }}>When OFF</strong><div style={{ marginTop: 4 }}>Module hidden from the sidebar, saved labels and audit history are preserved.</div></div>
          </div>
          {!canConfig && <p className="muted" style={{ fontSize: 12, marginTop: 10 }}><Icon name="lock" size={12} /> Only an Admin can change module availability.</p>}
        </SetCard>

        <SetCard title="Demo Mode" sub="An automated, screen-recordable product walkthrough that drives the real app, for sales demos, investor decks and trade shows." icon="play">
          <SetRow k="Show Demo Mode launcher" d="Adds a floating “Demo Mode” button (bottom-left) to start the guided walkthrough. Off by default.">
            <Switch defaultOn={(() => { try { return localStorage.getItem("nutridms_demo_enabled") === "1"; } catch (e) { return false; } })()} onChange={(v) => { if (!canConfig) return toast("Only an Admin can change this"); if (window.NutriDemo) window.NutriDemo.setLauncher(v); toast(v ? "Demo Mode launcher enabled" : "Demo Mode launcher hidden"); }} />
          </SetRow>
          <div style={{ display: "flex", gap: 20, flexWrap: "wrap", marginTop: 12, fontSize: 12.5, color: "var(--gray-600)" }}>
            <div><strong style={{ color: "var(--gray-800)" }}>When ON</strong><div style={{ marginTop: 4 }}>A floating launcher appears; Shift + D starts, Esc stops the walkthrough.</div></div>
            <div><strong style={{ color: "var(--gray-800)" }}>When OFF</strong><div style={{ marginTop: 4 }}>Launcher hidden everywhere, the app is unaffected.</div></div>
          </div>
          {!canConfig && <p className="muted" style={{ fontSize: 12, marginTop: 10 }}><Icon name="lock" size={12} /> Only an Admin can change this.</p>}
        </SetCard>

      </section>

      <section id="subscription-comparison" className="subscription-comparison subscription-scroll-target">
        <div className="sub-section-heading">
          <div><span>Detailed comparison</span><h3>See every entitlement</h3></div>
          <p>Compare access limits and operational capabilities across all NutriDMS tiers.</p>
        </div>
        <SetCard title="Plan comparison" sub="What's included at each tier, your current plan is highlighted." icon="grid">
          <div style={{ overflowX: "auto" }}>
            <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
              <thead><tr style={{ textAlign: "left", color: "var(--gray-500)", fontSize: 11.5, textTransform: "uppercase", letterSpacing: ".03em" }}>
                <th style={{ padding: "8px 10px" }}>Feature</th>{PLANS.map((item) => <th key={item.id} style={{ padding: "8px 10px", textAlign: "center" }}>{item.label}</th>)}
              </tr></thead>
              <tbody>
                {MATRIX.map((row) => (
                  <tr key={row.feature} style={{ borderTop: "1px solid var(--gray-100)" }}>
                    <td style={{ padding: "9px 10px", fontWeight: 600 }}>{row.feature}</td>
                    {PLANS.map((item) => <td key={item.id} style={{ padding: "9px 10px", textAlign: "center", background: plan === item.key ? "var(--green-50)" : "transparent" }}>{cell(row.included.has(item.key))}</td>)}
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </SetCard>
      </section>
    </div>
  );
}

/* Plan and top-up payments use Stripe-hosted checkout only. */
function SetSubmission({ toast }) {
  const [cuisine, setCuisine] = useStored("sub_cuisine", "American");
  const [units, setUnits] = useStored("sub_units", "metric");
  const [autosave, setAutosave] = useStored("sub_autosave", true);
  const [aiAssist, setAiAssist] = useStored("sub_ai", true);
  return (
    <>
      <SetCard title="Submission defaults" sub="Pre-fill these whenever you start a new recipe or ingredient." icon="utensils-crossed">
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
          <div className="field"><label>Default cuisine</label><select className="select" value={cuisine} onChange={e => setCuisine(e.target.value)}>{["American", "Mediterranean", "Asian", "Mexican", "Italian", "Indian"].map(c => <option key={c}>{c}</option>)}</select></div>
          <div className="field"><label>Measurement units</label><select className="select" value={units} onChange={e => setUnits(e.target.value)}><option value="metric">Metric (g, ml)</option><option value="imperial">Imperial (oz, cup)</option></select></div>
        </div>
      </SetCard>
      <SetCard title="Drafting" sub="How the recipe builder behaves while you work." icon="pencil">
        <SetRow k="Auto-save drafts" d="Keep a local draft every few seconds while editing"><Switch defaultOn={autosave} onChange={setAutosave} /></SetRow>
        <SetRow k="Loraa writing assist" d="Suggest descriptions and step wording as you type"><Switch defaultOn={aiAssist} onChange={setAiAssist} /></SetRow>
        <SetRow k="Submit reminders" d="Nudge me if a draft sits untouched for 3 days"><Switch defaultOn /></SetRow>
      </SetCard>
      <button className="btn primary" onClick={() => toast("Submission preferences saved")}><Icon name="check" size={14} /> Save preferences</button>
    </>
  );
}

function SetReview({ toast }) {
  const [region, setRegion] = useStored("rev_region", "FDA (USA)");
  const [autoclaim, setAutoclaim] = useStored("rev_autoclaim", false);
  return (
    <>
      <SetCard title="Nutrition review defaults" sub="Standards applied when you verify nutrition data." icon="clipboard-check">
        <div className="field"><label>Default nutrition standard</label><select className="select" value={region} onChange={e => setRegion(e.target.value)}>{["FDA (USA)", "Health Canada", "EFSA (Europe)", "FSANZ (Australia)"].map(c => <option key={c}>{c}</option>)}</select></div>
        <div className="field" style={{ marginTop: 12 }}><label>Macro variance tolerance</label><select className="select" defaultValue="5%"><option>2%</option><option>5%</option><option>10%</option></select><div className="muted" style={{ fontSize: 12, marginTop: 5 }}>Flag submissions whose macros differ from the source by more than this.</div></div>
      </SetCard>
      <SetCard title="Queue behavior" sub="How items reach your review queue." icon="inbox">
        <SetRow k="Auto-claim next item" d="Pull the next pending item automatically when you finish one"><Switch defaultOn={autoclaim} onChange={setAutoclaim} /></SetRow>
        <SetRow k="Show Loraa confidence" d="Display the AI confidence score on each item"><Switch defaultOn /></SetRow>
        <SetRow k="Hide already-approved" d="Collapse items that passed automated checks"><Switch /></SetRow>
      </SetCard>
      <button className="btn primary" onClick={() => toast("Review preferences saved")}><Icon name="check" size={14} /> Save preferences</button>
    </>
  );
}

function SetCompliance({ toast }) {
  const [region, setRegion] = useStored("cmp_region", "FDA (USA)");
  const [strict, setStrict] = useStored("cmp_strict", "balanced");
  return (
    <>
      <SetCard title="Regulatory defaults" sub="The framework your compliance reviews enforce by default." icon="shield-check">
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
          <div className="field"><label>Primary jurisdiction</label><select className="select" value={region} onChange={e => setRegion(e.target.value)}>{["FDA (USA)", "Health Canada", "EFSA (Europe)", "FSA (UK)", "FSANZ (Australia)", "Gulf (GCC)"].map(c => <option key={c}>{c}</option>)}</select></div>
          <div className="field"><label>Allergen strictness</label><select className="select" value={strict} onChange={e => setStrict(e.target.value)}><option value="strict">Strict, flag any trace</option><option value="balanced">Balanced</option><option value="lenient">Lenient</option></select></div>
        </div>
      </SetCard>
      <SetCard title="Escalation & disclosure" sub="When compliance issues route onward." icon="alert-triangle">
        <SetRow k="Auto-escalate critical failures" d="Send label-rule breaches straight to the compliance lead"><Switch defaultOn /></SetRow>
        <SetRow k="Require disclosure notes" d="Block approval until cross-contamination notes are attached"><Switch defaultOn /></SetRow>
        <SetRow k="Mandatory second sign-off" d="High-risk recipes need two compliance approvals"><Switch /></SetRow>
      </SetCard>
      <button className="btn primary" onClick={() => toast("Compliance defaults saved")}><Icon name="check" size={14} /> Save defaults</button>
    </>
  );
}

function SetEditorial({ toast }) {
  const [defAssignee, setDefAssignee] = useStored("mgr_assignee", "Auto (lightest load)");
  return (
    <>
      <SetCard title="Editorial defaults" sub="Defaults applied across the Assignments board and calendar." icon="users">
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
          <div className="field"><label>Default assignee</label><select className="select" value={defAssignee} onChange={e => setDefAssignee(e.target.value)}><option>Auto (lightest load)</option><option>Leave unassigned</option><option>Assign to me</option></select></div>
          <div className="field"><label>Default board view</label><select className="select" defaultValue="Board"><option>Board</option><option>List</option><option>Workload</option></select></div>
          <div className="field"><label>Calendar default</label><select className="select" defaultValue="Month"><option>Month</option><option>Week</option></select></div>
          <div className="field"><label>Review SLA (hours)</label><input className="input" type="number" defaultValue={12} /></div>
        </div>
      </SetCard>
      <SetCard title="Team workflow" sub="Routing rules for new submissions." icon="git-branch">
        <SetRow k="Round-robin assignment" d="Distribute new work evenly across the team"><Switch defaultOn /></SetRow>
        <SetRow k="Notify on overdue" d="Alert me when an assignment passes its due date"><Switch defaultOn /></SetRow>
        <SetRow k="Allow self-assign" d="Let contributors pull work from the backlog"><Switch /></SetRow>
      </SetCard>
      <button className="btn primary" onClick={() => toast("Editorial settings saved")}><Icon name="check" size={14} /> Save settings</button>
    </>
  );
}

function LoraaCreditsCard({ canConfig, toast }) {
  const [usage, setUsage] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [buying, setBuying] = React.useState("");
  const [showLog, setShowLog] = React.useState(true);
  const packs = [
    { id: "tokens_1m", tokens: 1000000, price: 29 },
    { id: "tokens_5m", tokens: 5000000, price: 119 },
    { id: "tokens_20m", tokens: 20000000, price: 399 },
  ];
  const refresh = React.useCallback(async () => {
    setLoading(true);
    try {
      const data = window.NutriLoraa && await window.NutriLoraa.usage(100);
      if (data) setUsage(data);
    } catch (error) {
      toast((error && error.message) || "Could not load Loraa usage.");
    } finally {
      setLoading(false);
    }
  }, [toast]);
  React.useEffect(() => {
    refresh();
    const h = () => refresh();
    window.addEventListener("nutridms-loraa-usage", h);
    return () => window.removeEventListener("nutridms-loraa-usage", h);
  }, [refresh]);
  const buy = async (pack) => {
    if (!canConfig) return toast("Only an Admin can purchase token packs.");
    setBuying(pack.id);
    try {
      const result = await window.NutriAPI.post("/billing/loraa/topup", {
        pack: pack.id,
        locale: (document.documentElement.lang || "en").split("-")[0],
      });
      if (!result || !result.checkout_url) throw new Error("Checkout is not available.");
      window.top.location.assign(result.checkout_url);
    } catch (error) {
      toast((error && error.message) || "Could not open secure checkout.");
    } finally {
      setBuying("");
    }
  };
  const saveThresholds = async (values) => {
    if (!canConfig) return;
    try {
      const next = await window.NutriLoraa.updateUsageAlerts(values);
      if (next) setUsage(next);
      toast("Loraa usage alerts updated.");
    } catch (error) {
      toast((error && error.message) || "Could not update usage alerts.");
    }
  };
  if (loading && !usage) {
    return <SetCard title="Loraa AI usage & billing" sub="Loading provider-reported token usage…" icon="lightbulb"><div className="saas-usage-skeleton" /></SetCard>;
  }
  if (!usage) {
    return <SetCard title="Loraa AI usage & billing" sub="Exact usage becomes available after the secure backend connects." icon="lightbulb"><div className="saas-empty">No live Loraa usage data is available yet.</div></SetCard>;
  }
  const fmt = (n) => Number(n || 0).toLocaleString();
  const pct = Math.min(100, Number(usage.percentUsed || 0));
  const tone = usage.alertLevel === "critical" || usage.alertLevel === "danger" ? "danger" : usage.alertLevel === "warning" ? "warning" : "healthy";
  return (
    <SetCard title="Loraa AI usage & billing" sub="Provider-reported tokens, request-level audit logs, top-ups and spend warnings. No browser estimates." icon="lightbulb">
      <div className={"saas-usage-hero " + tone}>
        <div>
          <span className="saas-eyebrow">Current billing period</span>
          <div className="saas-usage-number">{fmt(usage.remainingTokens)} <small>tokens remaining</small></div>
          <p>{fmt(usage.usedTokens)} used of {fmt(usage.allowanceTokens)} available · {usage.plan} plan</p>
        </div>
        <div className="saas-usage-ring" style={{ "--usage": pct + "%" }}><b>{pct.toFixed(1)}%</b><span>used</span></div>
      </div>
      <div className="saas-meter"><i className={tone} style={{ width: pct + "%" }} /></div>
      <div className="saas-kpi-grid">
        <div><span>Included</span><b>{fmt(usage.includedTokens)}</b></div>
        <div><span>Top-up balance</span><b>{fmt(usage.topupTokens)}</b></div>
        <div><span>Input tokens</span><b>{fmt(usage.inputTokens)}</b></div>
        <div><span>Output tokens</span><b>{fmt(usage.outputTokens)}</b></div>
        <div><span>Cached input</span><b>{fmt(usage.cachedInputTokens)}</b></div>
        <div><span>Reasoning</span><b>{fmt(usage.reasoningTokens)}</b></div>
      </div>
      <div className="saas-billing-row">
        <div>
          <b>Automatic usage alerts</b>
          <p>Warn organization admins before the token balance is exhausted.</p>
        </div>
        <div className="saas-thresholds">
          {[50, 75, 90].map((value) => (
            <button key={value} disabled={!canConfig} className={(usage.alertThresholds || []).includes(value) ? "active" : ""} onClick={() => {
              const current = usage.alertThresholds || [];
              const next = current.includes(value) ? current.filter((item) => item !== value) : current.concat(value).sort((a, b) => a - b);
              if (next.length) saveThresholds(next);
            }}>{value}%</button>
          ))}
        </div>
      </div>
      <div className="saas-pack-head"><div><b>Instant token top-ups</b><p>Secure one-time checkout. Purchased tokens are added only after Stripe confirms payment.</p></div></div>
      <div className="saas-pack-grid">
        {packs.map((pack) => <button key={pack.id} disabled={!canConfig || !!buying} onClick={() => buy(pack)}><span>{fmt(pack.tokens)} tokens</span><b>{buying === pack.id ? "Opening…" : "$" + pack.price}</b></button>)}
      </div>
      <div className="saas-log-head">
        <div><b>Exact usage log</b><span>{(usage.events || []).length} requests this period</span></div>
        <button className="btn secondary sm" onClick={() => setShowLog(!showLog)}>{showLog ? "Hide log" : "Show log"}</button>
      </div>
      {showLog && <div className="saas-usage-log">
        <div className="saas-log-row head"><span>When / user</span><span>Purpose</span><span>Model</span><span>Input</span><span>Output</span><span>Total</span></div>
        {(usage.events || []).length ? usage.events.map((event) => (
          <div className="saas-log-row" key={event.id}>
            <span><b>{new Date(event.createdAt).toLocaleString()}</b><small>{event.user}</small></span>
            <span><b>{event.feature} · {event.operation}</b><small>{event.purpose || "Loraa operation"}</small></span>
            <span>{event.model || "OpenAI"}</span>
            <span>{fmt(event.inputTokens)}<small>{event.cachedInputTokens ? fmt(event.cachedInputTokens) + " cached" : ""}</small></span>
            <span>{fmt(event.outputTokens)}<small>{event.reasoningTokens ? fmt(event.reasoningTokens) + " reasoning" : ""}</small></span>
            <span><b>{fmt(event.totalTokens)}</b><small>{fmt(event.latencyMs)} ms</small></span>
          </div>
        )) : <div className="saas-empty">No Loraa requests have been billed during this period.</div>}
      </div>}
      {!canConfig && <p className="muted" style={{ fontSize: 12, marginTop: 12 }}><Icon name="lock" size={12} /> Usage is visible to you; only Admins can change alerts or purchase tokens.</p>}
    </SetCard>
  );
}

function LiveBillingAddonCard({ addOn, title, sub, icon, plan, canConfig, toast, openLabel }) {
  const [row, setRow] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const load = React.useCallback(async () => {
    if (!window.NutriAPI) return;
    try {
      const payload = await window.NutriAPI.get("/billing/usage");
      setRow(payload && payload.add_ons ? payload.add_ons[addOn] : null);
    } catch (error) {
      toast && toast((error && error.message) || "Live add-on status could not be loaded.");
    }
  }, [addOn, plan]);
  React.useEffect(() => { load(); }, [load]);

  const applyClientState = (next) => {
    const enabled = !!(next && next.enabled);
    try {
      if (window.Entitlements && window.Entitlements.addon) {
        window.Entitlements.addon(addOn, enabled && !(next && next.included_by_plan));
      }
      if (addOn === "digital_signage") {
        localStorage.setItem("nutridms.digital_signage.settings", JSON.stringify({
          enabled, updatedAt: new Date().toISOString(), updatedBy: "NutriDMS billing",
        }));
      }
      if (addOn === "customer_experience" && window.CustomerEngagement) {
        window.CustomerEngagement.setAddonPurchased(enabled);
      }
      window.dispatchEvent(new Event("nutridms-entitlements"));
      window.dispatchEvent(new Event("nutridms-digital-signage"));
      window.dispatchEvent(new Event("nutridms-nav"));
    } catch (_) { }
  };
  const toggle = async (enabled) => {
    if (!canConfig) return toast("Only an organization Admin can manage paid add-ons.");
    setBusy(true);
    try {
      const payload = await window.NutriAPI.post("/billing/add-ons/" + encodeURIComponent(addOn), { enabled });
      const next = payload && payload.add_ons ? payload.add_ons[addOn] : null;
      if (!next) throw new Error("The billing service did not return the updated add-on.");
      setRow(next);
      if (window.Entitlements && window.Entitlements.applyBillingUsage) {
        window.Entitlements.applyBillingUsage(payload);
      }
      applyClientState(next);
      toast(title + (next.enabled ? " is now active." : " has been cancelled."));
    } catch (error) {
      toast((error && error.message) || (title + " could not be updated."));
      await load();
    } finally {
      setBusy(false);
    }
  };
  const enabled = !!(row && row.enabled);
  const includedByPlan = !!(row && row.included_by_plan);
  const price = row && row.monthly_price != null ? Number(row.monthly_price) : (addOn === "digital_signage" ? 15 : 120);
  return (
    <SetCard title={title} sub={sub} icon={icon}>
      <div style={{ display: "grid", gridTemplateColumns: "minmax(0, 1fr) auto", gap: 18, alignItems: "center", padding: "16px", border: "1px solid var(--green-200)", borderRadius: 14, background: "linear-gradient(135deg, var(--green-50), #fff)" }}>
        <div>
          <div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
            <strong style={{ fontSize: 15 }}>Organization add-on</strong>
            <span className={`pill ${enabled ? "success" : "neutral"}`} style={{ fontSize: 10 }}>{row ? (enabled ? "Active" : "Not active") : "Loading…"}</span>
            <span className="pill neutral" style={{ fontSize: 10 }}>{includedByPlan ? "Included with Enterprise" : `$${price} USD / month`}</span>
          </div>
          <p className="muted" style={{ fontSize: 12.5, lineHeight: 1.55, margin: "7px 0 0" }}>
            {includedByPlan ? "Included in the current plan." : "Stripe adds or removes this recurring item from the shared organization subscription."}
          </p>
        </div>
        <Switch key={`${addOn}-${enabled}-${busy}`} defaultOn={enabled} disabled={!canConfig || !row || busy} onChange={toggle} />
      </div>
      <SetRow k={includedByPlan ? "Plan entitlement" : "Monthly add-on charge"} d={includedByPlan ? "Included in Enterprise at no extra charge." : "Access is granted only after Stripe accepts the subscription change."}>
        <strong style={{ color: "var(--green-700)" }}>{includedByPlan ? "$0 included" : `$${price} USD/mo`}</strong>
      </SetRow>
      {openLabel && <div style={{ display: "flex", justifyContent: "flex-end", marginTop: 14 }}>
        <button className="btn primary" disabled={!enabled} onClick={() => {
          if (addOn === "digital_signage" && window.DigitalSignage && !window.DigitalSignage.open()) {
            toast("Your browser blocked the Digital Signage tab. Allow pop-ups and try again.");
          }
        }}><Icon name="monitor-play" size={15} /> {openLabel}</button>
      </div>}
      {!canConfig && <p className="muted" style={{ fontSize: 12, marginTop: 10 }}><Icon name="lock" size={12} /> Only an Admin can change paid add-ons.</p>}
    </SetCard>
  );
}

function StaffSeatsCard({ plan, canConfig, toast }) {
  const [summary, setSummary] = React.useState(null);
  const [error, setError] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const [quantity, setQuantity] = React.useState(0);
  const load = React.useCallback(async () => {
    if (!window.NutriAPI) return;
    try {
      const payload = await window.NutriAPI.get("/billing/usage");
      setSummary(payload || null);
      setQuantity(Number(payload && payload.paid_seats || 0));
      setError("");
    } catch (loadError) {
      setError((loadError && loadError.message) || "Live seat usage is unavailable.");
    }
  }, [plan]);
  React.useEffect(() => {
    load();
    const on = () => load();
    window.addEventListener("nutridms-members-changed", on);
    return () => window.removeEventListener("nutridms-members-changed", on);
  }, [load]);
  const used = summary ? Number(summary.used_seats || 0) : 0;
  const total = summary ? Number(summary.seat_limit || 0) : 0;
  const included = summary ? Number(summary.included_seats || 0) : 0;
  const paid = summary ? Number(summary.paid_seats || 0) : 0;
  const pct = total > 0 ? Math.round((used / total) * 100) : 0;
  const over = total > 0 && used > total;
  const barState = over ? "over" : pct >= 100 ? "full" : pct >= 80 ? "amber" : "green";
  const saveSeats = async () => {
    if (!canConfig || busy || plan === "enterprise") return;
    setBusy(true);
    try {
      const payload = await window.NutriAPI.post("/billing/seats", { quantity: Math.max(0, Number(quantity) || 0) });
      setSummary(payload);
      setQuantity(Number(payload && payload.paid_seats || 0));
      toast("Additional staff seats updated.");
      window.dispatchEvent(new Event("nutridms-members-changed"));
    } catch (saveError) {
      toast((saveError && saveError.message) || "Additional seats could not be updated.");
      await load();
    } finally { setBusy(false); }
  };
  return (
    <SetCard title="Staff seats" sub="Live active members and pending invitations. Enterprise locations share one 100-user allowance; accepted overage users are billed automatically." icon="users">
      <div className="seat-head">
        <div className="seat-count"><b>{summary ? used : "…"}</b> of <b>{summary ? total : "…"}</b> seats assigned</div>
        <span className={`pill ${barState === "green" ? "success" : barState === "amber" ? "warning" : "error"}`}>{summary ? pct : "…"}% used</span>
      </div>
      <div className="seat-bar"><i className={`seat-bar-fill ${barState}`} style={{ width: Math.min(100, pct) + "%" }} /></div>
      <div className="seat-facts">
        <span>{included || "—"} included with {plan === "loading" ? "the current plan" : plan.charAt(0).toUpperCase() + plan.slice(1)}</span>
        {paid > 0 && <span>{paid} paid additional</span>}
        <span>{Math.max(0, total - used)} available</span>
        {summary && summary.pending_invitations > 0 && <span>{summary.pending_invitations} reserved (pending invites)</span>}
      </div>
      {error && <div className="seat-over"><Icon name="alert-triangle" size={13} /> {error}</div>}
      {over && <div className="seat-over"><Icon name="alert-triangle" size={13} /> Seat capacity is exceeded; deactivate members or increase paid capacity.</div>}
      {canConfig && plan !== "enterprise" && <div className="seat-actions" style={{ alignItems: "end" }}>
        <label className="field" style={{ margin: 0, minWidth: 180 }}><span>Additional paid seats</span><input className="input" type="number" min="0" max="500" value={quantity} onChange={e => setQuantity(e.target.value)} /></label>
        <button className="btn primary" disabled={busy || !summary} onClick={saveSeats}><Icon name="credit-card" size={15} /> {busy ? "Updating Stripe…" : "Update seats"}</button>
      </div>}
      {canConfig && <div className="seat-actions">
        <button className="btn secondary" onClick={() => { window.__setPage && window.__setPage("users"); }}><Icon name="users" size={15} /> Manage members</button>
        {plan === "enterprise" && <span className="pill success">Enterprise overage seats bill automatically</span>}
      </div>}
    </SetCard>
  );
}
function DepartmentsCard({ toast, canEdit }) {
  const [list, setList] = React.useState([]);
  const [adding, setAdding] = React.useState("");
  const [busy, setBusy] = React.useState("");
  const load = React.useCallback(async () => {
    if (!window.NutriAPI) return;
    try {
      const payload = await window.NutriAPI.get("/invitations/departments/");
      setList(Array.isArray(payload && payload.results) ? payload.results : []);
    } catch (error) { toast && toast((error && error.message) || "Departments could not be loaded."); }
  }, []);
  React.useEffect(() => { load(); }, [load]);
  const add = async () => {
    const v = adding.trim(); if (!v || busy) return;
    if (list.some(d => String(d.name || "").toLowerCase() === v.toLowerCase())) { toast && toast("That department already exists"); return; }
    setBusy("add");
    try {
      await window.NutriAPI.post("/invitations/departments/", { name: v });
      setAdding(""); await load(); toast && toast("Added " + v);
    } catch (error) { toast && toast((error && error.message) || "Department could not be added."); }
    finally { setBusy(""); }
  };
  const remove = async (department) => {
    if (busy) return; setBusy(String(department.id));
    try {
      await window.NutriAPI.del("/invitations/departments/" + encodeURIComponent(department.id) + "/");
      await load(); toast && toast("Removed " + department.name);
    } catch (error) { toast && toast((error && error.message) || "Department could not be removed."); }
    finally { setBusy(""); }
  };
  return (
    <SetCard title="Departments" sub="Organize members into departments. Available when inviting members and in each member's profile." icon="network">
      {!canEdit && <div className="muted" style={{ fontSize: 12.5, marginBottom: 12 }}><Icon name="lock" size={12} /> Only an Admin can change departments.</div>}
      <div style={{ display: "flex", flexWrap: "wrap", gap: 8, marginBottom: canEdit ? 14 : 0 }}>
        {list.map(d => (
          <span key={d.id} className="dept-chip">{d.name}{d.manager_name ? " · " + d.manager_name : ""}{canEdit && <button disabled={!!busy} className="dept-chip-x" onClick={() => remove(d)} aria-label={"Remove " + d.name}><Icon name="x" size={12} /></button>}</span>
        ))}
        {list.length === 0 && <span className="muted" style={{ fontSize: 13 }}>No departments yet.</span>}
      </div>
      {canEdit && (
        <div style={{ display: "flex", gap: 8 }}>
          <input className="input" value={adding} onChange={e => setAdding(e.target.value)} onKeyDown={e => e.key === "Enter" && add()} placeholder="New department name" style={{ flex: 1 }} />
          <button className="btn secondary" disabled={!!busy} onClick={add}><Icon name="plus" size={15} /> {busy === "add" ? "Adding…" : "Add"}</button>
        </div>
      )}
    </SetCard>
  );
}

function OrganizationLogo({ workspace, canEdit, toast, onSaved }) {
  const fileRef = React.useRef(null);
  const [saving, setSaving] = React.useState(false);
  const [error, setError] = React.useState("");
  const logo = String(workspace && workspace.branding && workspace.branding.logo_url || "");

  const saveLogo = async (logoUrl) => {
    if (!workspace || !workspace.id || !canEdit || saving) return;
    setSaving(true); setError("");
    try {
      const current = workspace.branding && typeof workspace.branding === "object" ? workspace.branding : {};
      const updated = await window.NutriAPI.patch(
        "/organizations/" + encodeURIComponent(workspace.id) + "/",
        { branding: { ...current, logo_url: logoUrl, use_hq_branding: workspace.is_headquarters !== false } },
      );
      const next = updated && updated.id ? updated : { ...workspace, branding: { ...current, logo_url: logoUrl } };
      if (logoUrl) localStorage.setItem("nutridms_org_logo", logoUrl);
      else localStorage.removeItem("nutridms_org_logo");
      window.dispatchEvent(new Event("nutridms-org"));
      onSaved && onSaved(next);
      toast && toast(logoUrl ? "Company logo updated across this workspace." : "Company logo reset to NutriDMS.");
    } catch (saveError) {
      setError((saveError && saveError.message) || "The company logo could not be saved.");
    } finally {
      setSaving(false);
    }
  };

  const chooseFile = (event) => {
    const file = event.target.files && event.target.files[0];
    if (!file) return;
    if (!/^image\/(png|jpeg|webp)$/.test(file.type)) {
      setError("Use a PNG, JPG, or WebP image.");
      return;
    }
    if (file.size > 1024 * 1024) {
      setError("Keep the company logo under 1 MB.");
      return;
    }
    const reader = new FileReader();
    reader.onload = () => saveLogo(String(reader.result || ""));
    reader.readAsDataURL(file);
    event.target.value = "";
  };

  return (
    <div style={{ display: "flex", gap: 18, alignItems: "center", flexWrap: "wrap" }}>
      <div style={{ width: 76, height: 76, borderRadius: 18, overflow: "hidden", display: "grid", placeItems: "center", flexShrink: 0, background: logo ? "#fff" : "var(--brand-700)", backgroundImage: logo ? `url("${logo}")` : "none", backgroundSize: "contain", backgroundRepeat: "no-repeat", backgroundPosition: "center", boxShadow: "inset 0 0 0 1px var(--gray-200)" }}>
        {!logo && <img src="/brand/nutridms-mark.svg" alt="" width="44" height="44" />}
      </div>
      <div style={{ flex: 1, minWidth: 240 }}>
        <strong style={{ display: "block", fontSize: 14 }}>Company logo</strong>
        <p className="muted" style={{ fontSize: 12.5, margin: "4px 0 12px", lineHeight: 1.5 }}>Shown in the navigation and branded workspace surfaces. Use a square transparent PNG, JPG, or WebP image under 1 MB.</p>
        {canEdit ? <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
          <button className="btn secondary sm" disabled={saving || !workspace} onClick={() => fileRef.current && fileRef.current.click()}><Icon name="upload" size={13} /> {saving ? "Saving…" : (logo ? "Replace logo" : "Upload logo")}</button>
          {logo && <button className="btn ghost sm" disabled={saving} onClick={() => saveLogo("")}><Icon name="rotate-ccw" size={13} /> Reset</button>}
          <input ref={fileRef} hidden type="file" accept="image/png,image/jpeg,image/webp" onChange={chooseFile} />
        </div> : <span className="muted" style={{ fontSize: 12.5 }}><Icon name="lock" size={12} /> Only Admins and Super Admins can change the company logo.</span>}
        {error && <div role="alert" style={{ color: "var(--red-600, #d92d20)", fontSize: 12, marginTop: 8 }}>{error}</div>}
      </div>
    </div>
  );
}

function SetOrganization({ toast, role }) {
  const canBrand = role === "admin" || role === "super-admin";
  const isSuperAdmin = role === "super-admin";
  const enterpriseLocations = pageEntitled("workspace");
  const [workspace, setWorkspace] = React.useState(null);
  const [billing, setBilling] = React.useState(null);

  React.useEffect(() => {
    let mounted = true;
    const load = async () => {
      try {
        if (isSuperAdmin && enterpriseLocations && window.NutriWorkspaces) {
          const hub = await window.NutriWorkspaces.hub();
          if (!mounted) return;
          const activeId = String((hub && hub.active_workspace_id) || "");
          const active = ((hub && hub.workspaces) || []).find((item) => String(item.id) === activeId)
            || ((hub && hub.workspaces) || [])[0] || null;
          setWorkspace(active); setBilling(hub && hub.billing || null);
          return;
        }
        const me = await window.NutriAuth.me();
        if (!mounted) return;
        const activeId = String((me && me.active_organization_id) || "");
        const membership = ((me && me.memberships) || []).find((item) => String(item.organization && item.organization.id) === activeId)
          || ((me && me.memberships) || [])[0] || null;
        const organization = membership && membership.organization || null;
        setWorkspace(organization);
        setBilling(organization ? { plan: organization.subscription_tier } : null);
      } catch (loadError) {
        if (mounted) toast && toast((loadError && loadError.message) || "Organization details could not be loaded.");
      }
    };
    load();
    return () => { mounted = false; };
  }, [isSuperAdmin, enterpriseLocations]);

  return (
    <>
      <SetCard title="Company profile & logo" sub="Your headquarters identity is available on every paid plan." icon="image">
        <OrganizationLogo workspace={workspace} canEdit={canBrand} toast={toast} onSaved={setWorkspace} />
      </SetCard>
      <SetCard title="Workspace" sub={enterpriseLocations ? "Enterprise location, data, sync, billing, member, and log controls." : "Your current plan includes one headquarters workspace."} icon="building-2">
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(190px,1fr))", gap: 12 }}>
          <div className="field"><label>Headquarters workspace</label><input className="input" value={workspace ? workspace.name : "Loading…"} readOnly /></div>
          <div className="field"><label>Location code</label><input className="input" value={workspace ? workspace.location_code || "HQ" : "Loading…"} readOnly /></div>
          <div className="field"><label>Plan</label><input className="input" value={billing ? String(billing.plan || "").replace(/^./, (c) => c.toUpperCase()) : "Loading…"} readOnly /></div>
          <div className="field"><label>Region</label><input className="input" value={workspace ? workspace.region || "—" : "Loading…"} readOnly /></div>
        </div>
        {isSuperAdmin && enterpriseLocations
          ? <button className="btn primary" style={{ marginTop: 14 }} onClick={() => window.dispatchEvent(new CustomEvent("nutridms-open-workspace"))}><Icon name="settings" size={14} /> Manage Enterprise locations</button>
          : <div className="muted" style={{ marginTop: 12, fontSize: 12.5 }}><Icon name="lock" size={13} /> Workspace switching and additional locations are available only on Enterprise.</div>}
      </SetCard>
      <DepartmentsCard toast={toast} canEdit={canBrand} />
    </>
  );
}

/* Compliance & Publishing, org gate toggles (Master PRD §10). Admin/Super-admin edit; others view. */
function CxAccessCodes({ canEdit, toast }) {
  const ce = window.CustomerEngagement;
  const [rows, setRows] = React.useState(() => (ce ? ce.acAll() : []));
  const [type, setType] = React.useState("LOCATION");
  const [loc, setLoc] = React.useState("");
  const refresh = () => setRows(ce.acAll().slice());
  if (!ce) return null;
  const create = () => {
    if (!canEdit) return toast("Only an Admin can change this");
    ce.acCreate({ type, location: loc, orgName: "Fresh Kitchen", destinationType: type === "ORGANIZATION" ? "LOCATION_SELECT" : "TODAYS_MENU" });
    setLoc(""); refresh(); toast("Access code generated");
  };
  const act = (id, fn, msg) => { if (!canEdit) return toast("Only an Admin can change this"); fn(); refresh(); toast(msg); };
  return (
    <SetCard title="Access Codes" sub="Short codes customers type to open your experience — one resolver serves codes and QR alike." icon="ticket">
      <div className="cx-ac-new">
        <select className="input sm" value={type} onChange={(e) => setType(e.target.value)} disabled={!canEdit}>
          {["ORGANIZATION", "LOCATION", "MENU", "OFFERING", "EVENT", "CATERING", "TEMPORARY"].map((t) => <option key={t} value={t}>{t[0] + t.slice(1).toLowerCase()}</option>)}
        </select>
        <input className="input sm" placeholder="Location (optional)" value={loc} onChange={(e) => setLoc(e.target.value)} disabled={!canEdit} />
        <button className="btn primary sm" onClick={create} disabled={!canEdit}><Icon name="plus" size={14} /> Generate code</button>
      </div>
      <div className="cx-ac-table">
        <div className="cx-ac-head"><span>Code</span><span>Type</span><span>Location</span><span>Status</span><span>Uses</span><span>Actions</span></div>
        {rows.map((c) => (
          <div key={c.id} className="cx-ac-row">
            <span className="cx-ac-code">{c.code}</span>
            <span>{c.type[0] + c.type.slice(1).toLowerCase()}</span>
            <span>{c.location || "—"}</span>
            <span><span className={"pill " + (c.active ? "success" : "neutral")} style={{ fontSize: 10 }}>{c.active ? "Active" : "Inactive"}</span></span>
            <span>{(c.uses || 0).toLocaleString()}</span>
            <span className="cx-ac-actions">
              <button title="Copy" onClick={() => { try { navigator.clipboard.writeText(c.code); } catch (e) { } toast("Code copied"); }}><Icon name="copy" size={14} /></button>
              <button title={c.active ? "Deactivate" : "Activate"} onClick={() => act(c.id, () => ce.acUpdate(c.id, { active: !c.active }), c.active ? "Deactivated" : "Activated")}><Icon name={c.active ? "pause" : "play"} size={14} /></button>
              <button title="Regenerate" onClick={() => act(c.id, () => ce.acRegenerate(c.id), "Regenerated")}><Icon name="refresh-cw" size={14} /></button>
            </span>
          </div>
        ))}
      </div>
    </SetCard>
  );
}

function SetCustomerExperience({ toast, role }) {
  const canEdit = role === "admin" || role === "super-admin";
  const CE = window.CustomerEngagement;
  const [s, setS] = React.useState(() => (CE ? CE.settings() : {}));
  const [seg, setSeg] = React.useState("general");
  if (!CE) return <div className="set-page"><div className="empty">Customer engagement engine not loaded.</div></div>;
  const patch = (p) => { if (!canEdit) return toast("Only an Admin can change this"); const n = CE.setSettings(p); setS(Object.assign({}, n)); };
  const patchGroup = (key, p) => patch({ [key]: Object.assign({}, s[key], p) });
  const SEGS = [["general", "General"], ["accesscodes", "Access Codes"], ["thankyou", "Thank You Page"], ["registration", "Registration"], ["reviews", "Reviews"], ["loyalty", "Loyalty"], ["menu", "Menu"]];

  return (
    <div className="set-page">
      <h2 className="set-h">Customer Experience</h2>
      <p className="set-sub">Configure the public QR portal, thank-you page, reviews, loyalty and menu. Gated behind your plan tier and the feature toggle below.</p>
      {!canEdit && <div className="alert" style={{ marginBottom: 16 }}><Icon name="lock" size={18} /><div><strong>View only.</strong><div style={{ marginTop: 2 }}>Only an Admin or Super Admin can change these.</div></div></div>}

      <SetCard title="Customer Engagement Portal" sub="Master switch + minimum plan tier for the public QR experience and Customer CRM." icon="smartphone">
        <SetRow k="Enable portal" d="Turns on the QR experience, Customer CRM, loyalty, reviews and promotions.">
          <Switch defaultOn={!!s.enabled} disabled={!canEdit} onChange={(v) => patch({ enabled: v })} />
        </SetRow>
        <SetRow k="Minimum tier" d="Plan required to use the portal.">
          <select className="input" value={s.tier} disabled={!canEdit} onChange={(e) => patch({ tier: e.target.value })}>
            {["professional", "business", "enterprise"].map((t) => <option key={t} value={t}>{t[0].toUpperCase() + t.slice(1)}</option>)}
          </select>
        </SetRow>
      </SetCard>

      <div className="cx-seg">{SEGS.map(([id, l]) => <button key={id} className={"cx-seg-btn" + (seg === id ? " on" : "")} onClick={() => setSeg(id)}>{l}</button>)}</div>

      {seg === "thankyou" && (
        <SetCard title="Thank You Page" sub="Shown after viewing or ordering." icon="heart-handshake">
          <SetRow k="Enable" d="Show a thank-you page."><Switch defaultOn={!!s.thankYou.enabled} disabled={!canEdit} onChange={(v) => patchGroup("thankYou", { enabled: v })} /></SetRow>
          <div className="set-fld"><label>Header</label><input className="input" value={s.thankYou.header} disabled={!canEdit} onChange={(e) => patchGroup("thankYou", { header: e.target.value })} /></div>
          <div className="set-fld"><label>Message</label><textarea className="input" rows={2} value={s.thankYou.message} disabled={!canEdit} onChange={(e) => patchGroup("thankYou", { message: e.target.value })} /></div>
          <SetRow k="Show rating prompt" d="Invite a star rating."><Switch defaultOn={!!s.thankYou.showRating} disabled={!canEdit} onChange={(v) => patchGroup("thankYou", { showRating: v })} /></SetRow>
          <div className="set-fld"><label>Coupon text (optional)</label><input className="input" value={s.thankYou.coupon} disabled={!canEdit} placeholder="e.g. 10% off your next visit" onChange={(e) => patchGroup("thankYou", { coupon: e.target.value })} /></div>
        </SetCard>
      )}
      {seg === "registration" && (
        <SetCard title="Customer Registration" sub="Never forced unless you require it." icon="user-plus">
          <SetRow k="Mode" d="How registration is offered.">
            <select className="input" value={s.registration.mode} disabled={!canEdit} onChange={(e) => patchGroup("registration", { mode: e.target.value })}>
              <option value="off">Off (guest only)</option><option value="optional">Optional</option><option value="required">Required</option>
            </select>
          </SetRow>
          <SetRow k="Show benefits" d="List account benefits."><Switch defaultOn={!!s.registration.benefits} disabled={!canEdit} onChange={(v) => patchGroup("registration", { benefits: v })} /></SetRow>
          <div className="set-fld"><label>Fields collected</label>
            <div className="cx-chips">{["firstName", "lastName", "email", "phone", "birthday", "allergies", "diet"].map((f) => {
              const on = (s.registration.fields || []).includes(f);
              return <button key={f} className={"cx-chip" + (on ? " on" : "")} disabled={!canEdit} onClick={() => patchGroup("registration", { fields: on ? s.registration.fields.filter((x) => x !== f) : s.registration.fields.concat([f]) })}>{f}</button>;
            })}</div>
          </div>
        </SetCard>
      )}
      {seg === "reviews" && (
        <SetCard title="Reviews" sub="Configurable questions, rating types and moderation." icon="star">
          <SetRow k="Enable reviews"><Switch defaultOn={!!s.reviews.enabled} disabled={!canEdit} onChange={(v) => patchGroup("reviews", { enabled: v })} /></SetRow>
          <SetRow k="Allow guest reviews" d="Anonymous, non-registered customers."><Switch defaultOn={!!s.reviews.guest} disabled={!canEdit} onChange={(v) => patchGroup("reviews", { guest: v })} /></SetRow>
          <SetRow k="Waiter / server ratings"><Switch defaultOn={!!s.reviews.waiterRatings} disabled={!canEdit} onChange={(v) => patchGroup("reviews", { waiterRatings: v })} /></SetRow>
          <SetRow k="Default rating type">
            <select className="input" value={s.reviews.ratingType} disabled={!canEdit} onChange={(e) => patchGroup("reviews", { ratingType: e.target.value })}>
              {["stars", "emoji", "thumbs", "1-10"].map((t) => <option key={t} value={t}>{t}</option>)}
            </select>
          </SetRow>
          <SetRow k="Moderation">
            <select className="input" value={s.reviews.moderation} disabled={!canEdit} onChange={(e) => patchGroup("reviews", { moderation: e.target.value })}>
              <option value="auto">Auto-publish</option><option value="manual">Manager approval</option>
            </select>
          </SetRow>
        </SetCard>
      )}
      {seg === "loyalty" && (
        <SetCard title="Loyalty" sub="Points, campaigns and tiers." icon="award">
          <SetRow k="Enable loyalty"><Switch defaultOn={!!s.loyalty.enabled} disabled={!canEdit} onChange={(v) => patchGroup("loyalty", { enabled: v })} /></SetRow>
          <SetRow k="Points per dollar"><input className="input sm" type="number" value={s.loyalty.pointsPerDollar} disabled={!canEdit} onChange={(e) => patchGroup("loyalty", { pointsPerDollar: +e.target.value || 0 })} /></SetRow>
          <SetRow k="Birthday reward (pts)"><input className="input sm" type="number" value={s.loyalty.birthdayReward} disabled={!canEdit} onChange={(e) => patchGroup("loyalty", { birthdayReward: +e.target.value || 0 })} /></SetRow>
          <SetRow k="Referral reward (pts)"><input className="input sm" type="number" value={s.loyalty.referralReward} disabled={!canEdit} onChange={(e) => patchGroup("loyalty", { referralReward: +e.target.value || 0 })} /></SetRow>
          <SetRow k="Double-point day">
            <select className="input" value={s.loyalty.doublePointDay} disabled={!canEdit} onChange={(e) => patchGroup("loyalty", { doublePointDay: e.target.value })}>
              {["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", "None"].map((d) => <option key={d} value={d}>{d}</option>)}
            </select>
          </SetRow>
          <div className="cx-tierlist">{s.loyalty.tiers.map((t, i) => (
            <div key={i} className="cx-tierrow"><b>{t.name}</b><input className="input sm" type="number" value={t.min} disabled={!canEdit} onChange={(e) => { const tiers = s.loyalty.tiers.slice(); tiers[i] = Object.assign({}, t, { min: +e.target.value || 0 }); patchGroup("loyalty", { tiers: tiers }); }} /><span>{t.perk}</span></div>
          ))}</div>
        </SetCard>
      )}
      {seg === "menu" && (
        <SetCard title="Today's Menu" sub="Time-based menu shown on scan." icon="utensils-crossed">
          <SetRow k="Enable menu"><Switch defaultOn={!!s.menu.enabled} disabled={!canEdit} onChange={(v) => patchGroup("menu", { enabled: v })} /></SetRow>
          <SetRow k="Show prices"><Switch defaultOn={!!s.menu.showPrices} disabled={!canEdit} onChange={(v) => patchGroup("menu", { showPrices: v })} /></SetRow>
          <SetRow k="Enable ordering"><Switch defaultOn={!!s.menu.ordering} disabled={!canEdit} onChange={(v) => patchGroup("menu", { ordering: v })} /></SetRow>
          <SetRow k="Hide sold out"><Switch defaultOn={!!s.menu.hideSoldOut} disabled={!canEdit} onChange={(v) => patchGroup("menu", { hideSoldOut: v })} /></SetRow>
          {Object.keys(s.menu.schedule).map((slot) => (
            <div key={slot} className="set-fld"><label>{slot} hours</label><input className="input" value={s.menu.schedule[slot]} disabled={!canEdit} placeholder="06:00-11:00" onChange={(e) => patchGroup("menu", { schedule: Object.assign({}, s.menu.schedule, { [slot]: e.target.value }) })} /></div>
          ))}
        </SetCard>
      )}
      {seg === "accesscodes" && <CxAccessCodes canEdit={canEdit} toast={toast} />}
      {seg === "general" && (
        <SetCard title="Customer Preferences" sub="Allergy/dislike prompt on scan + marketing consent options." icon="sliders-horizontal">
          <p className="set-sub" style={{ margin: "0 0 8px" }}>The scan-time allergy & dislike prompt and the energy chart / side selection are configured in the live preview under Customer Mobile View. Marketing-consent options offered at registration:</p>
          <div className="cx-chips">{["Promotions", "Nutrition updates", "Newsletter", "Coupons", "SMS"].map((c) => {
            const on = (s.consentOptions || []).includes(c);
            return <button key={c} className={"cx-chip" + (on ? " on" : "")} disabled={!canEdit} onClick={() => patch({ consentOptions: on ? s.consentOptions.filter((x) => x !== c) : (s.consentOptions || []).concat([c]) })}>{c}</button>;
          })}</div>
        </SetCard>
      )}
    </div>
  );
}

/* Compliance & Publishing, org gate toggles (Master PRD §10). Admin/Super-admin edit; others view. */
function SetCompliancePublishing({ toast, role }) {
  const canEdit = role === "admin" || role === "super-admin";
  const [cfg, setCfg] = React.useState(() => (typeof psSettings === "function" ? psSettings() : {}));
  const set = (key, val) => {
    if (!canEdit) return;
    if (typeof psSetSetting === "function") psSetSetting(key, val);
    setCfg((c) => ({ ...c, [key]: val }));
    toast(`${val ? "Now required" : "No longer required"} before approval`);
  };
  const Toggle = ({ on, k }) => (
    canEdit
      ? <Switch defaultOn={on} onChange={(v) => set(k, v)} />
      : <span className={`pill ${on ? "success" : "neutral"}`} style={{ fontSize: 10 }}>{on ? "Required" : "Optional"}</span>
  );
  const gateRows = [
    { k: "requireProductSpecBeforeApproval", t: "Product details complete", d: "Brand, SKU, category, country of origin, and net weight must be filled in." },
    { k: "requirePackagingBeforeApproval", t: "Packaging complete", d: "Package type, material, unit weight, and case pack must be specified." },
    { k: "requireShelfLifeBeforeApproval", t: "Storage & shelf life complete", d: "Storage type and shelf-life days are required." },
    { k: "requireManufacturingBeforeApproval", t: "Manufacturing details complete", d: "Facility name and country of manufacture are required." },
    { k: "requireGS1BeforeApproval", t: "GS1 GTIN assigned", d: "A unique GTIN must be assigned in GS1 & Barcodes." },
    { k: "requireBarcodeBeforeApproval", t: "Barcode generated", d: "A scannable barcode must be generated for the assigned GTIN." },
    { k: "requireAllergenReviewBeforeApproval", t: "Allergen declaration reviewed", d: "A reviewer must confirm the allergen declaration." },
    { k: "requireLabelBeforeApproval", t: "Label generated & locked", d: "A Health-Canada label must be generated and locked in Label Studio." },
  ];
  return (
    <>
      {!canEdit && (
        <div className="alert" style={{ marginBottom: 16 }}>
          <Icon name="lock" size={18} />
          <div><strong>View only.</strong><div style={{ marginTop: 2 }}>These organization-wide rules can only be changed by an Admin or Super Admin.</div></div>
        </div>
      )}
      <SetCard title="Product Specifications" sub="Turn the Product Specification tabs on for product, combo, and catering offerings." icon="clipboard-list">
        <SetRow k="Enable Product Specifications" d="Adds Product Details, Packaging, Storage, Manufacturing, GS1, QR, Compliance, and Documents tabs to offerings.">
          <Toggle on={cfg.enableProductSpecifications} k="enableProductSpecifications" />
        </SetRow>
      </SetCard>
      <SetCard title="Required before approval" sub="Any requirement left on must be complete before an offering can be approved or published. NutriDMS blocks approval and lists what's missing." icon="shield-check">
        {gateRows.map((r) => (
          <SetRow key={r.k} k={r.t} d={r.d}><Toggle on={cfg[r.k]} k={r.k} /></SetRow>
        ))}
      </SetCard>
      <div className="muted" style={{ fontSize: 12.5, display: "flex", alignItems: "center", gap: 6 }}>
        <Icon name="info" size={13} /> These gates are enforced live on each offering's Compliance tab and on the approval action.
      </div>
    </>
  );
}

function SetReferenceIds({ toast, role }) {
  const canEdit = ["admin", "super-admin"].includes(role);
  const [cfg, setCfg] = React.useState(() => (typeof refIdConfig === "function" ? refIdConfig() : {}));
  const [loading, setLoading] = React.useState(true);
  const [saving, setSaving] = React.useState(false);
  const [saveError, setSaveError] = React.useState("");
  const set = (p) => setCfg(c => ({ ...c, ...p }));
  const ex = (kind) => (typeof refIdFormat === "function" ? refIdFormat(kind, kind === "ingredient" ? (cfg.ingredientStart || 1) : (cfg.recipeStart || 1), cfg) : "");

  const connected = () => {
    try { return !!(window.NutriData && window.NutriData.isConnected && window.NutriData.isConnected()); } catch (e) { return false; }
  };
  const settingsApi = () => {
    try { return (window.NutriData && window.NutriData.settings) || window.NutriSettings || null; } catch (e) { return null; }
  };

  React.useEffect(() => {
    let active = true;
    const load = async () => {
      if (!connected()) {
        if (active) setLoading(false);
        return;
      }
      try {
        const settings = settingsApi();
        if (!settings || typeof settings.referenceIds !== "function") throw new Error("Reference-ID settings are unavailable.");
        const remote = await settings.referenceIds();
        if (!active || !remote) return;
        const next = typeof saveRefIdConfig === "function" ? saveRefIdConfig(remote) : remote;
        setCfg(next);
      } catch (error) {
        if (active) setSaveError((error && error.message) || "Reference-ID settings could not be loaded.");
      } finally {
        if (active) setLoading(false);
      }
    };
    load();
    return () => { active = false; };
  }, []);

  const save = async () => {
    setSaving(true);
    setSaveError("");
    try {
      if (!connected()) throw new Error("Reconnect to save reference-ID settings for the currently selected workspace.");
      const settings = settingsApi();
      if (!settings || typeof settings.saveReferenceIds !== "function") throw new Error("Reference-ID settings are unavailable.");
      const saved = await settings.saveReferenceIds({ ...cfg, configured: true });
      if (!saved) throw new Error("NutriDMS did not save the reference-ID settings.");
      const next = typeof saveRefIdConfig === "function" ? saveRefIdConfig({ ...saved, configured: true }) : { ...saved, configured: true };
      setCfg(next);
      toast("Reference ID format saved for the currently selected workspace");
    } catch (error) {
      setSaveError((error && error.message) || "Reference-ID settings could not be saved.");
    } finally {
      setSaving(false);
    }
  };
  const SEPARATORS = [["-", "Dash · RCP-001"], ["_", "Underscore · RCP_001"], ["/", "Slash · RCP/001"], ["", "None · RCP001"]];

  return (
    <>
      {loading && <div className="muted" style={{ marginBottom: 12 }}>Loading the organization reference-ID settings…</div>}
      {saveError && <div className="alert warning" style={{ marginBottom: 16 }}>{saveError}</div>}
      {!loading && !cfg.configured && (
        <div className="alert warning" style={{ marginBottom: 16 }}>
          <Icon name="alert-triangle" size={18} />
          <div>
            <strong>Set this up before creating content.</strong>
            <div style={{ marginTop: 2 }}>Recipes and ingredients can't be created until the currently selected workspace's reference-ID format is configured and saved.</div>
          </div>
        </div>
      )}

      <SetCard title="Reference ID format" sub="Every recipe and ingredient is assigned a unique, human-readable ID for auditability. Define the format your organization uses." icon="hash">
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
          <div className="field"><label>Recipe prefix</label>
            <input className="input" value={cfg.recipePrefix || ""} disabled={!canEdit} maxLength={6}
              onChange={(e) => set({ recipePrefix: e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, "") })} placeholder="RCP" /></div>
          <div className="field"><label>Ingredient prefix</label>
            <input className="input" value={cfg.ingredientPrefix || ""} disabled={!canEdit} maxLength={6}
              onChange={(e) => set({ ingredientPrefix: e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, "") })} placeholder="ING" /></div>
          <div className="field"><label>Separator</label>
            <select className="select" value={cfg.separator} disabled={!canEdit} onChange={(e) => set({ separator: e.target.value })}>
              {SEPARATORS.map(([v, l]) => <option key={v} value={v}>{l}</option>)}
            </select></div>
          <div className="field"><label>Number padding (digits)</label>
            <select className="select" value={cfg.padding} disabled={!canEdit} onChange={(e) => set({ padding: parseInt(e.target.value, 10) })}>
              {[3, 4, 5, 6, 7, 8].map((n) => <option key={n} value={n}>{n} digits · {String(1).padStart(n, "0")}</option>)}
            </select></div>
          <div className="field"><label>Recipe start number</label>
            <input className="input" type="number" min="1" value={cfg.recipeStart || 1} disabled={!canEdit}
              onChange={(e) => set({ recipeStart: Math.max(1, parseInt(e.target.value || "1", 10)) })} /></div>
          <div className="field"><label>Ingredient start number</label>
            <input className="input" type="number" min="1" value={cfg.ingredientStart || 1} disabled={!canEdit}
              onChange={(e) => set({ ingredientStart: Math.max(1, parseInt(e.target.value || "1", 10)) })} /></div>
        </div>

        <div className="refid-preview">
          <div className="refid-preview-h"><Icon name="eye" size={13} stroke={2.4} /> Live preview</div>
          <div className="refid-preview-row">
            <span className="refid-preview-k">Recipe</span>
            <span className="ref-badge md"><Icon name="hash" size={13} stroke={2.4} /><span className="ref-badge-num">{ex("recipe")}</span></span>
            <Icon name="arrow-right" size={13} className="refid-preview-arrow" />
            <span className="ref-badge md"><Icon name="hash" size={13} stroke={2.4} /><span className="ref-badge-num">{refIdFormat("recipe", (cfg.recipeStart || 1) + 1, cfg)}</span></span>
          </div>
          <div className="refid-preview-row">
            <span className="refid-preview-k">Ingredient</span>
            <span className="ref-badge md"><Icon name="hash" size={13} stroke={2.4} /><span className="ref-badge-num">{ex("ingredient")}</span></span>
            <Icon name="arrow-right" size={13} className="refid-preview-arrow" />
            <span className="ref-badge md"><Icon name="hash" size={13} stroke={2.4} /><span className="ref-badge-num">{refIdFormat("ingredient", (cfg.ingredientStart || 1) + 1, cfg)}</span></span>
          </div>
        </div>
      </SetCard>

      <SetCard title="Importing existing IDs" sub="When you upload a spreadsheet that already has its own unique ID for each item, NutriDMS can keep those IDs instead of generating new ones." icon="file-spreadsheet">
        <SetRow k="Allow imported IDs to override" d="During CSV import, map an ID column to use your existing references. Imported IDs are kept verbatim and skip the generated format.">
          <Switch key={String(cfg.allowImportOverride)} defaultOn={cfg.allowImportOverride} onChange={(v) => set({ allowImportOverride: v })} disabled={!canEdit || saving} />
        </SetRow>
        <div className="refid-note">
          <Icon name="info" size={15} stroke={2.2} />
          <span>Override is applied per-row at import time in <strong>Bulk Import (CSV)</strong>. Rows without an ID fall back to the format above. Duplicate IDs are flagged before commit.</span>
        </div>
      </SetCard>

      {canEdit
        ? <button className="btn primary" onClick={save} disabled={saving}><Icon name="check" size={14} /> {saving ? "Saving…" : (cfg.configured ? "Save reference ID format" : "Save & enable content creation")}</button>
        : <div className="refid-note"><Icon name="lock" size={15} stroke={2.2} /><span>Only an Admin or Super Admin can change the reference ID format.</span></div>}
    </>
  );
}

function SetRecycleBin({ role, toast }) {
  const canManage = role === "admin" || role === "super-admin";
  const [q, setQ] = React.useState("");
  const [kind, setKind] = React.useState("all");
  const [items, setItems] = React.useState([]);
  const [state, setState] = React.useState("loading");
  const [busy, setBusy] = React.useState("");
  const [confirm, setConfirm] = React.useState(null);
  const load = React.useCallback(async () => {
    if (!window.NutriSettings || !window.NutriSettings.recycleBin) {
      setState("error");
      return;
    }
    setState("loading");
    try {
      const payload = await window.NutriSettings.recycleBin();
      setItems((payload && payload.results) || []);
      setState("ready");
    } catch (error) {
      setState("error");
      toast((error && error.message) || "Recycle Bin could not be loaded");
    }
  }, [toast]);
  React.useEffect(() => {
    load();
  }, [load]);
  const visibleItems = items
    .filter((item) => kind === "all" || item.resource === kind)
    .filter((item) => !q || (item.name || "").toLowerCase().includes(q.toLowerCase()) || (item.reference_id || "").toLowerCase().includes(q.toLowerCase()));
  const retention = window.BIN_RETENTION_DAYS || 90;
  const fmt = (ts) => { try { return new Date(ts).toLocaleDateString("en-GB", { day: "2-digit", month: "short", year: "numeric" }); } catch (e) { return "—"; } };
  const daysLeft = (item) => {
    const deleted = new Date(item.deleted_at).getTime();
    if (!Number.isFinite(deleted)) return retention;
    return Math.max(0, Math.ceil((deleted + retention * 86400000 - Date.now()) / 86400000));
  };
  const restore = async (item) => {
    setBusy("restore:" + item.id);
    try {
      await window.NutriSettings.restoreRecycleBinItem(item.resource, item.id);
      setItems((current) => current.filter((row) => row.id !== item.id));
      toast(`Restored “${item.name}”`);
    } catch (error) {
      toast((error && error.message) || "This item could not be restored");
    } finally {
      setBusy("");
    }
  };
  const purge = async (item) => {
    setBusy("purge:" + item.id);
    try {
      await window.NutriSettings.permanentlyDeleteRecycleBinItem(item.resource, item.id);
      setItems((current) => current.filter((row) => row.id !== item.id));
      toast(`Permanently deleted “${item.name}”`);
    } catch (error) {
      toast((error && error.message) || "This item could not be permanently deleted");
    } finally {
      setBusy("");
      setConfirm(null);
    }
  };

  return (
    <>
      <SetCard title="Recycle Bin" sub={`Deleted recipes and ingredients are kept here for ${retention} days, then permanently removed automatically. Restore returns an item to its library; permanent deletion cannot be undone.`} icon="trash-2">
        <div style={{ display: "flex", gap: 10, flexWrap: "wrap", alignItems: "center", marginBottom: 14 }}>
          <div className="search" style={{ flex: 1, minWidth: 220, maxWidth: 320 }}>
            <Icon name="search" size={16} />
            <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search deleted items…" />
          </div>
          <select className="select" style={{ width: 150 }} value={kind} onChange={(e) => setKind(e.target.value)}>
            <option value="all">All types</option>
            <option value="recipe">Recipes</option>
            <option value="ingredient">Ingredients</option>
          </select>
          <div className="grow" />
          <button className="btn secondary sm" onClick={load} disabled={state === "loading"}><Icon name="refresh-cw" size={14} /> Refresh</button>
        </div>

        {state === "error" ? (
          <div className="alert warning"><Icon name="alert-triangle" size={17} /><div>Recycle Bin is unavailable. Confirm you have Settings access for the active organization, then try again.</div></div>
        ) : state === "loading" ? (
          <div className="empty" style={{ padding: "40px 20px" }}><div className="icon"><Icon name="loader-circle" size={24} /></div><h3>Loading deleted items…</h3></div>
        ) : visibleItems.length === 0 ? (
          <div className="empty" style={{ padding: "40px 20px" }}>
            <div className="icon"><Icon name="trash-2" size={24} /></div>
            <h3>Recycle Bin is empty</h3>
            <p>{items.length ? "No deleted items match your filters." : `Deleted recipes and ingredients will appear here for ${retention} days.`}</p>
          </div>
        ) : (
          <div className="bin-list">
            {visibleItems.map((b) => {
              const days = daysLeft(b);
              const urgent = days <= 14;
              return (
                <div key={b.id} className="bin-row">
                  <span className={`bin-kind ${b.resource}`}><Icon name={b.resource === "ingredient" ? "leaf" : "utensils-crossed"} size={14} /></span>
                  <div className="bin-main">
                    <div className="bin-name">{b.name}</div>
                    <div className="bin-meta">
                      {b.reference_id && <span className="ref-badge"><Icon name="hash" size={11} stroke={2.4} /><span className="ref-badge-num">{b.reference_id}</span></span>}
                      <span className="bin-sub">{b.resource} · deleted {fmt(b.deleted_at)} · {b.workflow_status || "draft"}</span>
                    </div>
                  </div>
                  <span className={`bin-days ${urgent ? "urgent" : ""}`} title="Auto-deletes when this reaches 0"><Icon name="clock" size={12} stroke={2.4} /> {days}d left</span>
                  <div className="bin-actions">
                    <button className="btn secondary sm" disabled={!!busy} onClick={() => restore(b)}><Icon name="rotate-ccw" size={14} /> {busy === "restore:" + b.id ? "Restoring…" : "Restore"}</button>
                    {canManage && <button className="btn danger sm" disabled={!!busy} onClick={() => setConfirm(b)}><Icon name="trash-2" size={14} /> Delete forever</button>}
                  </div>
                </div>
              );
            })}
          </div>
        )}
        <div className="refid-note" style={{ marginTop: 14 }}>
          <Icon name="shield-alert" size={15} stroke={2.2} />
          <span>Permanent deletion is irreversible, NutriDMS cannot recover the item afterwards. Every delete, restore, and purge is recorded in the <strong>Audit Log</strong>.</span>
        </div>
      </SetCard>

      {confirm && <ConfirmDialog
        title={`Permanently delete “${confirm.name}”?`}
        body="This item will be destroyed immediately. This is unrecoverable, NutriDMS will not be able to restore it."
        confirmLabel="Delete forever"
        tone="danger" icon="trash-2"
        onCancel={() => setConfirm(null)}
        onConfirm={() => purge(confirm)} />}
    </>
  );
}

function SetCosting({ toast, role }) {
  const Link = window.NutriInvLink;
  const canEdit = role === "admin" || role === "super-admin";
  const [cfg, setCfg] = React.useState(() => (Link ? JSON.parse(JSON.stringify(Link.marginConfig())) : {}));
  const TYPES = [["product", "Products"], ["menu-item", "Menu Items"], ["combo", "Combo Meals"], ["meal-plan", "Meal Plans"], ["catering", "Catering"], ["recipe", "Recipes"]];
  const setT = (t, patch) => setCfg((c) => ({ ...c, [t]: { ...c[t], ...patch } }));
  const save = () => { Link.saveMarginConfig(cfg); toast && toast("Margin policy saved"); };
  if (!Link) return <div className="card pad"><h3>Costing &amp; margins</h3><p className="muted">Inventory module unavailable.</p></div>;
  return (
    <div className="col" style={{ gap: 16 }}>
      <div>
        <h2 style={{ fontFamily: "var(--serif)", fontSize: 22, margin: "0 0 4px" }}>Costing &amp; margins</h2>
        <p className="muted" style={{ fontSize: 13 }}>Set the target gross-margin used to estimate selling prices per product type — a single target or a low–high band. Estimated price = cost ÷ (1 − margin%).</p>
      </div>
      {TYPES.map(([id, label]) => {
        const c = cfg[id] || { mode: "single", value: 50 };
        return (
          <div className="card pad" key={id}>
            <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 12 }}>
              <b style={{ fontSize: 14 }}>{label}</b>
              <div className="seg" style={{ display: "inline-flex", gap: 4 }}>
                <button className={"btn sm " + (c.mode === "single" ? "primary" : "secondary")} disabled={!canEdit} onClick={() => setT(id, { mode: "single" })}>Single %</button>
                <button className={"btn sm " + (c.mode === "range" ? "primary" : "secondary")} disabled={!canEdit} onClick={() => setT(id, { mode: "range" })}>Range</button>
              </div>
            </div>
            {c.mode === "single" ? (
              <div className="cost-mrow"><label>Target margin</label><input type="number" className="input" disabled={!canEdit} value={c.value != null ? c.value : 50} onChange={(e) => setT(id, { value: Number(e.target.value) })} /><span>%</span></div>
            ) : (
              <div style={{ display: "flex", gap: 20, flexWrap: "wrap" }}>
                <div className="cost-mrow"><label>Low</label><input type="number" className="input" disabled={!canEdit} value={c.low != null ? c.low : 40} onChange={(e) => setT(id, { low: Number(e.target.value) })} /><span>%</span></div>
                <div className="cost-mrow"><label>High</label><input type="number" className="input" disabled={!canEdit} value={c.high != null ? c.high : 60} onChange={(e) => setT(id, { high: Number(e.target.value) })} /><span>%</span></div>
              </div>
            )}
          </div>
        );
      })}
      {canEdit ? <button className="btn primary" style={{ alignSelf: "flex-start" }} onClick={save}><Icon name="check" size={15} /> Save margin policy</button>
        : <div className="alert info"><Icon name="lock" size={15} /><div>View only — contact an Admin to change margin policy.</div></div>}
    </div>
  );
}

function SetLoadBalancing({ toast, role }) {
  const canEdit = ["admin", "super-admin"].includes(role);
  const [remoteWorkflow, setRemoteWorkflow] = React.useState(null);
  const [autoAssign, setAutoAssign] = React.useState(true);
  const [loading, setLoading] = React.useState(true);
  const [saving, setSaving] = React.useState(false);

  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const settings = window.NutriSettings && await window.NutriSettings.org();
        const workflow = settings && settings.publishingWorkflow;
        if (!cancelled && workflow && typeof workflow === "object") {
          setRemoteWorkflow(workflow);
          if (workflow.auto_assign_by_load != null) {
            setAutoAssign(!!workflow.auto_assign_by_load);
          }
        }
      } catch (_) {
        if (!cancelled) toast("Load-balancing policy could not be loaded");
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();
    return () => { cancelled = true; };
  }, [toast]);

  const save = async () => {
    if (saving || !canEdit) return;
    setSaving(true);
    try {
      if (!window.NutriSettings) throw new Error("Organization settings are unavailable.");
      const workflow = { ...(remoteWorkflow || {}), auto_assign_by_load: !!autoAssign };
      const saved = await window.NutriSettings.saveOrg({ publishingWorkflow: workflow });
      if (!saved) throw new Error("NutriDMS did not save the load-balancing policy.");
      setRemoteWorkflow((saved && saved.publishingWorkflow) || workflow);
      toast("Load-balancing policy saved");
    } catch (error) {
      toast((error && error.message) || "Load-balancing policy could not be saved");
    } finally {
      setSaving(false);
    }
  };
  return (
    <div className="enterprise-load-settings">
      <div className="card pad" style={{ marginBottom: 16, borderLeft: "3px solid var(--green-600)" }}>
        <div style={{ display: "flex", gap: 9 }}>
          <Icon name="info" size={16} style={{ color: "var(--green-700)", flexShrink: 0, marginTop: 1 }} />
          <p className="muted" style={{ fontSize: 13, margin: 0, lineHeight: 1.5 }}>When enabled, the server assigns new recipe and ingredient review submissions to the eligible reviewer with the fewest active assignments. Assignment is transactional, so simultaneous submissions cannot select the same stale load. <strong style={{ color: "var(--text-primary)" }}>Admin only.</strong></p>
        </div>
      </div>
      <SetCard title="Automatic reviewer assignment" sub="The count includes active pending-review content only. If no eligible reviewer is available, the submission is rejected with a clear action message instead of silently assigning the submitter." icon="scale">
        <SetRow k="Assign by least active load" d="Use server-side load counts to choose an eligible reviewer when a submission uses automatic routing."><Switch key={String(autoAssign)} defaultOn={autoAssign} disabled={loading || saving || !canEdit} onChange={setAutoAssign} /></SetRow>
        <div className="refid-note" style={{ marginTop: 14 }}><Icon name="lock" size={15} /><span>Manual reviewer assignment and shared-pool submission remain available to authorized users. Separate-approver policy is configured under Publishing Workflow.</span></div>
      </SetCard>
      {canEdit
        ? <button className="btn primary" onClick={save} disabled={loading || saving}><Icon name="check" size={14} /> {saving ? "Saving…" : "Save policy"}</button>
        : <div className="alert info"><Icon name="lock" size={15} /><div>View only — contact an Admin or Super Admin to change reviewer load-balancing.</div></div>}
    </div>
  );
}

function SetAccess({ toast }) {
  const { setPage } = useApp();
  return (
    <>
      <SetCard title="Security & access" sub="Account controls are applied by the NutriDMS security service, not stored in this browser." icon="lock">
        <div className="refid-note"><Icon name="shield-check" size={15} /><span>Manage your password, verified two-factor methods, active sessions and sign-in history below. Organization role changes are governed from People and Roles &amp; Permissions.</span></div>
        <button className="btn secondary sm" style={{ marginTop: 14 }} onClick={() => setPage("permissions")}><Icon name="users-round" size={14} /> Open Roles &amp; Permissions</button>
      </SetCard>
      <SetSecurity toast={toast} />
    </>
  );
}

function SetPlatform({ toast }) {
  return (
    <>
      <SetCard title="Platform controls" sub="Defaults for all customer accounts. Super Admin only." icon="server">
        <SetRow k="Maintenance mode" d="Show a maintenance banner to all customer accounts"><Switch /></SetRow>
        <SetRow k="Auto-setup new customers" d="Set up a new customer account automatically when approved"><Switch defaultOn /></SetRow>
        <SetRow k="Where customer data is stored" d="Region for new customer accounts"><select className="select" defaultValue="US" style={{ width: 120 }}><option>US</option><option>EU</option><option>APAC</option></select></SetRow>
      </SetCard>
      <SetCard title="AI / Loraa" sub="Platform-level AI governance." icon="lightbulb">
        <SetRow k="Global AI confidence floor" d="Escalate below this score across all customer accounts"><select className="select" defaultValue="0.82" style={{ width: 100 }}><option>0.75</option><option>0.82</option><option>0.90</option></select></SetRow>
        <SetRow k="Model drift alerts" d="Notify platform ops on drift detection"><Switch defaultOn /></SetRow>
      </SetCard>
      <button className="btn primary" onClick={() => toast("Platform settings saved")}><Icon name="check" size={14} /> Save settings</button>
    </>
  );
}

function SetLoraaMessenger({ role, toast }) {
  const [policy, setPolicy] = React.useState(LORAA_MESSENGER_DEFAULT);
  const [savedPolicy, setSavedPolicy] = React.useState(LORAA_MESSENGER_DEFAULT);
  const [loading, setLoading] = React.useState(true);
  const [saving, setSaving] = React.useState(false);
  const registeredRole = String(window.__nutridmsAuthenticatedUser && window.__nutridmsAuthenticatedUser.djangoRole || "")
    .trim().toLowerCase().replace(/\s+/g, "_");
  const canEdit = ["admin", "super-admin"].includes(role) ||
    ["admin", "super_admin", "super-admin"].includes(registeredRole);
  const dirty = policy.enabled !== savedPolicy.enabled || policy.displaySeconds !== savedPolicy.displaySeconds;
  const durationOptions = [
    [5, "5 seconds"],
    [10, "10 seconds"],
    [15, "15 seconds"],
    [30, "30 seconds"],
    [60, "1 minute"],
    [0, "Until dismissed"]
  ];

  React.useEffect(() => {
    let cancelled = false;
    const load = async () => {
      setLoading(true);
      try {
        const settings = window.NutriSettings && await window.NutriSettings.org();
        const next = normalizeLoraaMessengerPolicy(settings && settings.loraaMessenger);
        if (!cancelled) {
          setPolicy(next);
          setSavedPolicy(next);
        }
      } catch (_) {
        if (!cancelled) toast("Loraa Messenger settings could not be loaded");
      } finally {
        if (!cancelled) setLoading(false);
      }
    };
    load();
    return () => { cancelled = true; };
  }, []);

  const save = async () => {
    if (!canEdit || saving || !dirty || !window.NutriSettings) return;
    setSaving(true);
    try {
      const response = await window.NutriSettings.saveOrg({ loraaMessenger: policy });
      if (!response) throw new Error("No settings response");
      const next = normalizeLoraaMessengerPolicy(response.loraaMessenger || policy);
      setPolicy(next);
      setSavedPolicy(next);
      window.dispatchEvent(new CustomEvent("nutridms-org-settings", {
        detail: { loraaMessenger: next }
      }));
      toast("Loraa Messenger policy saved for the organization");
    } catch (_) {
      setPolicy(savedPolicy);
      toast("Could not save the Loraa Messenger policy");
    } finally {
      setSaving(false);
    }
  };

  const durationLabel = durationOptions.find(([value]) => value === policy.displaySeconds);
  return (
    <SetCard
      title="Loraa Messenger"
      sub="Control organization-wide Loraa reminders and how long they remain on screen."
      icon="message-circle"
    >
      <div className={`lmp-policy-hero ${policy.enabled ? "is-enabled" : "is-disabled"}`}>
        <div className="lmp-policy-icon"><img src="assets/loraa-logo.png" alt="" /></div>
        <div className="lmp-policy-copy">
          <span className="lmp-policy-eyebrow">Company policy</span>
          <strong>{policy.enabled ? "Messenger is enabled" : "Messenger is disabled"}</strong>
          <p>
            {policy.enabled
              ? `Loraa can show targeted reminders and nudges to signed-in team members for ${durationLabel ? durationLabel[1] : "15 seconds"}.`
              : "Loraa reminders stay in the notification center, but no popup appears on team members’ screens."}
          </p>
        </div>
        <button
          type="button"
          className={`lmp-policy-switch ${policy.enabled ? "on" : ""}`}
          role="switch"
          aria-checked={policy.enabled}
          aria-label="Enable Loraa Messenger"
          disabled={!canEdit || loading || saving}
          onClick={() => setPolicy((current) => ({ ...current, enabled: !current.enabled }))}
        >
          <span />
        </button>
      </div>

      <SetRow
        k="Popup display time"
        d="Choose how long each reminder stays visible before it closes automatically."
      >
        <select
          className="select lmp-duration-select"
          value={policy.displaySeconds}
          disabled={!canEdit || loading || saving || !policy.enabled}
          onChange={(event) => setPolicy((current) => ({
            ...current,
            displaySeconds: Number(event.target.value)
          }))}
        >
          {durationOptions.map(([value, label]) => <option key={value} value={value}>{label}</option>)}
        </select>
      </SetRow>

      <div className="lmp-policy-note">
        <Icon name="shield-check" size={16} />
        <span>
          This setting applies to every user in the active organization. Only a Company Admin or Super Admin can change it.
        </span>
      </div>

      <div className="lmp-policy-actions">
        <span>{loading ? "Loading company policy…" : dirty ? "You have unsaved changes" : "Company policy is up to date"}</span>
        <button className="btn primary" disabled={!canEdit || loading || saving || !dirty} onClick={save}>
          <Icon name={saving ? "loader-circle" : "check"} size={14} />
          {saving ? "Saving…" : "Save company policy"}
        </button>
      </div>
    </SetCard>
  );
}

function SetNotifications({ role, toast }) {
  const base = [
    { k: "Recipe approved", d: "When one of your recipes is approved" },
    { k: "Feedback received", d: "When a reviewer asks for changes" },
    { k: "Weekly digest", d: "A summary of your team's activity" },
  ];
  const byRole = {
    "reviewer": [{ k: "New review assigned", d: "When an item lands in your queue" }, { k: "Low AI confidence", d: "When Loraa flags a submission for human review" }],
    "compliance": [{ k: "Compliance failure", d: "When a recipe breaches a label rule" }, { k: "Escalation", d: "When an item is escalated to you" }],
    "manager": [{ k: "Overdue assignment", d: "When work passes its due date" }, { k: "Overloaded teammate", d: "When someone exceeds the workload cap" }],
    "admin": [{ k: "New subscriber", d: "When an organization requests access" }, { k: "Billing event", d: "Invoices, overages, and renewals" }],
    "super-admin": [{ k: "Platform incident", d: "Infrastructure or security alerts" }, { k: "New subscriber", d: "When an organization requests access" }],
  };
  const items = [...base, ...(byRole[role] || [])];
  return (
    <SetCard title="Email & in-app notifications" sub="Choose what NutriDMS notifies you about." icon="bell">
      {items.map(n => <SetRow key={n.k} k={n.k} d={n.d}><Switch defaultOn /></SetRow>)}
      <button className="btn primary" style={{ marginTop: 16 }} onClick={() => toast("Notification preferences saved")}><Icon name="check" size={14} /> Save preferences</button>
    </SetCard>
  );
}

function SetAppearance({ toast, role }) {
  const fire = () => {
    try { window.dispatchEvent(new Event("nutridms-appearance")); } catch (e) { }
    if (!window.NutriAppearance || !window.NutriAppearance.save) return;
    window.clearTimeout(window.__nutridmsAppearanceSaveTimer);
    window.__nutridmsAppearanceSaveTimer = window.setTimeout(() => {
      window.NutriAppearance.save().catch(() => { });
    }, 450);
  };
  const [density, setDensity] = React.useState(() => localStorage.getItem("nutridms.appearance.density") || "regular");
  const [start, setStart] = React.useState(() => localStorage.getItem("nutridms.app.sidebar.collapsed") === "1" ? "collapsed" : "expanded");
  const [navLayout, setNavLayout] = React.useState(() => localStorage.getItem("nutridms.nav.layout") || "sidebar");
  const [theme, setThemeLocal] = React.useState(() => { try { return document.documentElement.getAttribute("data-theme") || localStorage.getItem("nutridms.theme") || "light"; } catch (e) { return "light"; } });
  const applyTheme = (t) => { setThemeLocal(t); try { document.documentElement.setAttribute("data-theme", t); document.querySelector(".app")?.setAttribute("data-theme", t); localStorage.setItem("nutridms.theme", t); } catch (e) { } fire(); };
  const applyDensity = (d) => { setDensity(d); try { localStorage.setItem("nutridms.appearance.density", d); } catch (e) { } fire(); };
  const applyStart = (s) => { setStart(s); try { localStorage.setItem("nutridms.app.sidebar.collapsed", s === "collapsed" ? "1" : "0"); } catch (e) { } fire(); };
  const applyNav = (n) => { setNavLayout(n); try { localStorage.setItem("nutridms.nav.layout", n); } catch (e) { } fire(); };
  const [navColor, setNavColor] = React.useState(() => localStorage.getItem("nutridms.nav.color") || "dark-green");
  // Sidebar color id → matching CTA color id (used when sync is on).
  const NAV_TO_CTA = { "midnight": "green", "dark-green": "forest", "white": "green", "cobalt": "cobalt", "violet": "violet", "teal": "teal", "slate": "slate" };
  const applyNavColor = (c) => { setNavColor(c); try { localStorage.setItem("nutridms.nav.color", c); } catch (e) { } if (topbarSync) { try { localStorage.setItem("nutridms.nav.topbarColor", c); } catch (e) { } applyCta(NAV_TO_CTA[c] || "green"); } fire(); };
  const [topbarColor, setTopbarColor] = React.useState(() => localStorage.getItem("nutridms.nav.topbarColor") || "white");
  const applyTopbarColor = (c) => { setTopbarColor(c); try { localStorage.setItem("nutridms.nav.topbarColor", c); } catch (e) { } fire(); };
  const [topbarSync, setTopbarSync] = React.useState(() => localStorage.getItem("nutridms.nav.topbarSync") === "1");
  const applyTopbarSync = (v) => { setTopbarSync(v); try { localStorage.setItem("nutridms.nav.topbarSync", v ? "1" : "0"); if (v) { localStorage.setItem("nutridms.nav.topbarColor", navColor); setTopbarColor(navColor); applyCta(NAV_TO_CTA[navColor] || "green"); } } catch (e) { } fire(); };
  // CTA button color — platform-wide, available to everyone.
  const CTA_SWATCHES = [["green", "#1B7528"], ["forest", "#0E5A2A"], ["navy", "#1E3A8A"], ["teal", "#0E9384"], ["violet", "#6938EF"], ["cobalt", "#2A54E5"], ["slate", "#334155"], ["amber", "#B54708"]];
  const CTA_MAP = { "green": ["#1B7528", "#15631F", "#268A38"], "forest": ["#0E5A2A", "#0A4720", "#137038"], "navy": ["#1E3A8A", "#172E6E", "#2549A8"], "teal": ["#0E9384", "#0a6e63", "#12A594"], "violet": ["#6938EF", "#5022c0", "#7C52F5"], "cobalt": ["#2A54E5", "#1d3eb0", "#3B66F0"], "slate": ["#334155", "#25303f", "#3f4d5e"], "amber": ["#B54708", "#8f3806", "#D6620E"] };
  const [ctaColor, setCtaColor] = React.useState(() => localStorage.getItem("nutridms.cta.color") || "green");
  const applyCta = (c) => { setCtaColor(c); try { localStorage.setItem("nutridms.cta.color", c); const v = CTA_MAP[c] || CTA_MAP.green; const r = document.documentElement; r.style.setProperty("--brand-700", v[0]); r.style.setProperty("--brand-800", v[1]); r.style.setProperty("--brand-600", v[2]); } catch (e) { } fire(); };
  // Page skin — recolors the whole workspace surfaces.
  const SKINS = [["default", "Default"], ["dark-green", "Dark green"], ["navy-green", "Navy green"]];
  const [skin, setSkin] = React.useState(() => localStorage.getItem("nutridms.skin") || "default");
  const applySkin = (s) => { setSkin(s); try { localStorage.setItem("nutridms.skin", s); const el = document.querySelector(".app") || document.documentElement; el.setAttribute("data-skin", s); const dark = s === "dark-green" || s === "navy-green"; const base = localStorage.getItem("nutridms.theme") || "light"; const t = dark ? "dark" : base; document.documentElement.setAttribute("data-theme", t); el.setAttribute("data-theme", t); } catch (e) { } fire(); };
  React.useEffect(() => {
    const sync = () => {
      const appearance = window.NutriAppearance && window.NutriAppearance.current ? window.NutriAppearance.current() : null;
      if (!appearance) return;
      setThemeLocal(appearance.theme);
      setDensity(appearance.density);
      setStart(appearance.sidebarCollapsed ? "collapsed" : "expanded");
      setNavLayout(appearance.navLayout);
      setNavColor(appearance.navColor);
      setTopbarColor(appearance.topbarColor);
      setTopbarSync(appearance.topbarSync);
    };
    window.addEventListener("nutridms-appearance", sync);
    return () => window.removeEventListener("nutridms-appearance", sync);
  }, []);

  // Advanced palettes are part of the live Enterprise white-label entitlement.
  const whiteLabel = (role === "admin" || role === "super-admin")
    && !!(window.Entitlements && window.Entitlements.has("white_label"));
  const baseSwatches = [["midnight", "#3B7C0F"], ["dark-green", "#0E1612"], ["white", "#FFFFFF"]];
  const wlSwatches = [["cobalt", "#2A54E5"], ["violet", "#6938EF"], ["teal", "#0E9384"], ["slate", "#3a4757"]];
  const swatches = whiteLabel ? [...baseSwatches, ...wlSwatches] : baseSwatches;
  return (
    <SetCard title="Appearance" sub="Personalize how the workspace looks for you. Changes apply instantly." icon="palette">
      <SetRow k="Sidebar color" d={whiteLabel ? "Navigation sidebar color, full palette unlocked for white-label" : "Navigation sidebar color (dark or light green)"}>
        <div style={{ display: "flex", gap: 8 }}>
          {swatches.map(([id, c]) => (
            <button key={id} onClick={() => applyNavColor(id)} title={id.replace("-", " ")}
              style={{ width: 26, height: 26, borderRadius: 8, background: c, cursor: "pointer", border: navColor === id ? "2px solid var(--green-700)" : "2px solid transparent", boxShadow: navColor === id ? "0 0 0 2px #fff inset" : "none" }} />
          ))}
        </div>
      </SetRow>
      <SetRow k="Sync menu bar with sidebar" d="Match the top menu bar to the sidebar color automatically">
        <Switch defaultOn={topbarSync} onChange={applyTopbarSync} />
      </SetRow>
      {!topbarSync && (
        <SetRow k="Menu bar color" d="Top menu bar color, independent of the sidebar">
          <div style={{ display: "flex", gap: 8 }}>
            {swatches.map(([id, c]) => (
              <button key={id} onClick={() => applyTopbarColor(id)} title={id.replace("-", " ")}
                style={{ width: 26, height: 26, borderRadius: 8, background: c, cursor: "pointer", border: topbarColor === id ? "2px solid var(--green-700)" : "2px solid transparent", boxShadow: topbarColor === id ? "0 0 0 2px #fff inset" : "none" }} />
            ))}
          </div>
        </SetRow>
      )}
      <SetRow k="Density" d="Spacing of lists and cards">
        <div className="kb-typeseg" style={{ transform: "scale(.9)" }}>
          {["comfortable", "regular", "compact"].map(d => <button key={d} className={density === d ? "on" : ""} onClick={() => applyDensity(d)} style={{ textTransform: "capitalize", padding: "7px 12px" }}>{d}</button>)}
        </div>
      </SetRow>
      <SetRow k="Sidebar on load" d="Start expanded or collapsed">
        <div className="kb-typeseg" style={{ transform: "scale(.9)" }}>
          {["expanded", "collapsed"].map(d => <button key={d} className={start === d ? "on" : ""} onClick={() => applyStart(d)} style={{ textTransform: "capitalize", padding: "7px 12px" }}>{d}</button>)}
        </div>
      </SetRow>
      <button className="btn primary" style={{ marginTop: 16 }} onClick={async () => {
        window.clearTimeout(window.__nutridmsAppearanceSaveTimer);
        try {
          if (!window.NutriAppearance || !window.NutriAppearance.save) throw new Error("Account settings are unavailable.");
          await window.NutriAppearance.save();
          toast("Appearance synced across your devices");
        } catch (error) {
          toast((error && error.message) || "Appearance could not be synced");
        }
      }}><Icon name="check" size={14} /> Save appearance</button>
    </SetCard>
  );
}

function SetSecurity({ toast, sessionsOnly = false }) {
  const [sessions, setSessions] = React.useState([]);
  const [methods, setMethods] = React.useState([]);
  const [history, setHistory] = React.useState([]);
  const [state, setState] = React.useState("loading");
  const [busy, setBusy] = React.useState("");
  const [passwords, setPasswords] = React.useState({ current: "", next: "", confirm: "" });
  const [enrollment, setEnrollment] = React.useState({ method_type: "email", identifier: "", challenge_id: "", code: "" });

  const loadSecurity = React.useCallback(async (silent) => {
    if (!window.NutriAuth || !window.NutriSettings) {
      setState("error");
      return;
    }
    if (!silent) setState("loading");
    try {
      const [sessionsPayload, methodsPayload, historyPayload] = await Promise.all([
        window.NutriAuth.sessions(),
        window.NutriSettings.twoFactorMethods(),
        window.NutriSettings.loginHistory(),
      ]);
      setSessions((sessionsPayload && sessionsPayload.results) || (Array.isArray(sessionsPayload) ? sessionsPayload : []));
      setMethods((methodsPayload && methodsPayload.results) || (Array.isArray(methodsPayload) ? methodsPayload : []));
      setHistory((historyPayload && historyPayload.results) || (Array.isArray(historyPayload) ? historyPayload : []));
      setState("ready");
    } catch (error) {
      setState("error");
      if (!silent) toast((error && error.message) || "Security details could not be loaded");
    }
  }, [toast]);

  React.useEffect(() => {
    loadSecurity(false);
  }, [loadSecurity]);

  const revoke = async (session) => {
    setBusy("session:" + session.id);
    try {
      await window.NutriAuth.revokeSession(session.id);
      setSessions((rows) => rows.filter((row) => row.id !== session.id));
      toast((session.device_name || "Device") + " signed out");
    } catch (error) {
      toast((error && error.message) || "That device could not be signed out");
    } finally {
      setBusy("");
    }
  };
  const dateText = (value, fallback) => {
    if (!value) return fallback || "Time unavailable";
    const date = new Date(value);
    return Number.isNaN(date.getTime()) ? (fallback || "Time unavailable") : date.toLocaleString();
  };
  const changePassword = async () => {
    if (!passwords.current || !passwords.next || !passwords.confirm) {
      toast("Enter your current password and the new password twice.");
      return;
    }
    setBusy("password");
    try {
      await window.NutriSettings.changePassword({ current_password: passwords.current, new_password: passwords.next, confirm_password: passwords.confirm });
      toast("Password updated. Sign in again to continue.");
      if (window.NutriAPI && window.NutriAPI.tokens) window.NutriAPI.tokens.clear();
      window.setTimeout(() => { try { window.top.location.assign("/en/signin?reason=password-changed"); } catch (_) { window.location.assign("/en/signin?reason=password-changed"); } }, 700);
    } catch (error) {
      toast((error && error.message) || "Password could not be updated");
    } finally {
      setBusy("");
    }
  };
  const startEnrollment = async () => {
    setBusy("mfa-start");
    try {
      const payload = await window.NutriSettings.startTwoFactorEnrollment({ method_type: enrollment.method_type, identifier: enrollment.identifier || undefined });
      setEnrollment((current) => ({ ...current, challenge_id: payload.challenge_id, code: "" }));
      toast("Verification code sent. Enter it below to enable two-factor authentication.");
    } catch (error) {
      toast((error && error.message) || "Two-factor enrollment could not be started");
    } finally {
      setBusy("");
    }
  };
  const verifyEnrollment = async () => {
    if (!enrollment.challenge_id || !enrollment.code) return;
    setBusy("mfa-verify");
    try {
      await window.NutriSettings.verifyTwoFactorEnrollment({ challenge_id: enrollment.challenge_id, code: enrollment.code });
      setEnrollment({ method_type: "email", identifier: "", challenge_id: "", code: "" });
      toast("Two-factor authentication method verified.");
      await loadSecurity(true);
    } catch (error) {
      toast((error && error.message) || "The verification code was not accepted");
    } finally {
      setBusy("");
    }
  };
  const removeMethod = async (method) => {
    setBusy("mfa-remove:" + method.id);
    try {
      await window.NutriSettings.removeTwoFactorMethod(method.id);
      setMethods((rows) => rows.filter((row) => row.id !== method.id));
      toast("Two-factor method removed.");
    } catch (error) {
      toast((error && error.message) || "That two-factor method could not be removed");
    } finally {
      setBusy("");
    }
  };

  const sessionsPanel = (
    <div>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 12, marginBottom: 10 }}>
        <div>
          <div style={{ fontWeight: 700, fontSize: 14 }}>Active sessions</div>
          <div className="muted" style={{ fontSize: 12.5, marginTop: 2 }}>
            {state === "loading" ? "Checking your signed-in devices…"
              : sessions.length + " active device" + (sessions.length === 1 ? "" : "s")}
          </div>
        </div>
        <button className="btn secondary sm" onClick={() => loadSecurity(false)} disabled={state === "loading"}>
          <Icon name="refresh-cw" size={12} /> Refresh
        </button>
      </div>

      {state === "error" && (
        <div className="alert warning" role="alert" style={{ marginBottom: 10 }}>
          <Icon name="alert-triangle" size={16} />
          <div>Active sessions are temporarily unavailable. Your current session remains secure.</div>
        </div>
      )}
      {state === "ready" && sessions.length === 0 && (
        <div className="muted" style={{ padding: "12px 0", fontSize: 13 }}>
          No managed device sessions yet. Sign out and sign in once to register this device.
        </div>
      )}
      {sessions.map((session) => (
        <div key={session.id} style={{ display: "flex", alignItems: "center", gap: 12, padding: "12px 0", borderTop: "1px solid var(--gray-100)" }}>
          <div className="stat-icon brand" style={{ width: 34, height: 34, borderRadius: 9, flexShrink: 0 }}>
            <Icon name="monitor-smartphone" size={17} />
          </div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ display: "flex", alignItems: "center", gap: 7, flexWrap: "wrap" }}>
              <strong style={{ fontSize: 13.5 }}>{session.device_name || "Unknown device"}</strong>
            </div>
            <div className="muted" style={{ fontSize: 12, marginTop: 2 }}>
              {[session.user_agent, session.ip_address].filter(Boolean).join(" · ")}
            </div>
            <div className="muted" style={{ fontSize: 11.5, marginTop: 2 }}>Last active {dateText(session.last_seen_at || session.created_at)}</div>
          </div>
          <button className="btn secondary sm" onClick={() => revoke(session)} disabled={busy === "session:" + session.id} style={{ color: "var(--error-600)" }}>
            <Icon name="log-out" size={12} /> {busy === "session:" + session.id ? "Signing out…" : "Sign out"}
          </button>
        </div>
      ))}
    </div>
  );

  if (sessionsOnly) {
    return (
      <SetCard title="Active devices" sub="Review and revoke computers signed in to your account." icon="monitor-smartphone">
        {sessionsPanel}
      </SetCard>
    );
  }

  return (
    <SetCard title="Password & security" sub="Keep your account secure." icon="key-round">
      <div className="field" style={{ marginBottom: 12 }}><label>Current password</label><input className="input" value={passwords.current} onChange={(event) => setPasswords((current) => ({ ...current, current: event.target.value }))} type="password" autoComplete="current-password" placeholder="••••••••" /></div>
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
        <div className="field"><label>New password</label><input className="input" value={passwords.next} onChange={(event) => setPasswords((current) => ({ ...current, next: event.target.value }))} type="password" autoComplete="new-password" placeholder="At least 10 characters" /></div>
        <div className="field"><label>Confirm new password</label><input className="input" value={passwords.confirm} onChange={(event) => setPasswords((current) => ({ ...current, confirm: event.target.value }))} type="password" autoComplete="new-password" placeholder="••••••••" /></div>
      </div>
      <button className="btn primary" style={{ marginTop: 16 }} disabled={busy === "password"} onClick={changePassword}><Icon name="check" size={14} /> {busy === "password" ? "Updating…" : "Update password"}</button>

      <div style={{ borderTop: "1px solid var(--gray-100)", marginTop: 20, paddingTop: 16 }}>
        <div style={{ fontWeight: 700, fontSize: 14 }}>Two-factor authentication</div>
        <p className="muted" style={{ fontSize: 12.5, margin: "3px 0 12px" }}>Enrollment is complete only after a server-issued verification code is confirmed.</p>
        {methods.length ? <div style={{ display: "grid", gap: 8, marginBottom: 12 }}>{methods.map((method) => <div key={method.id} style={{ display: "flex", alignItems: "center", gap: 10, padding: "9px 0", borderBottom: "1px solid var(--gray-100)" }}><Icon name="shield-check" size={16} style={{ color: "var(--green-700)" }} /><div style={{ flex: 1 }}><strong style={{ fontSize: 13 }}>{method.method_type === "sms" ? "SMS" : "Email"}</strong><div className="muted" style={{ fontSize: 12 }}>{method.identifier} · {method.is_verified ? "verified" : "awaiting verification"}{method.is_primary ? " · primary" : ""}</div></div><button className="btn ghost sm" disabled={busy === "mfa-remove:" + method.id} onClick={() => removeMethod(method)}>Remove</button></div>)}</div> : <div className="muted" style={{ fontSize: 12.5, marginBottom: 12 }}>No verified two-factor method is configured.</div>}
        {!enrollment.challenge_id ? <div style={{ display: "flex", gap: 8, flexWrap: "wrap", alignItems: "end" }}><div className="field" style={{ minWidth: 130 }}><label>Method</label><select className="select" value={enrollment.method_type} onChange={(event) => setEnrollment((current) => ({ ...current, method_type: event.target.value }))}><option value="email">Email</option><option value="sms">SMS</option></select></div>{enrollment.method_type === "sms" && <div className="field" style={{ minWidth: 220, flex: 1 }}><label>Mobile number</label><input className="input" value={enrollment.identifier} onChange={(event) => setEnrollment((current) => ({ ...current, identifier: event.target.value }))} placeholder="+15551234567" /></div>}<button className="btn secondary" disabled={busy === "mfa-start"} onClick={startEnrollment}><Icon name="send" size={14} /> {busy === "mfa-start" ? "Sending…" : "Add method"}</button></div> : <div className="alert info" style={{ alignItems: "end", flexWrap: "wrap" }}><Icon name="key-round" size={17} /><div style={{ flex: 1 }}><strong>Enter the six-digit verification code</strong><input className="input" value={enrollment.code} onChange={(event) => setEnrollment((current) => ({ ...current, code: event.target.value.replace(/\D/g, "").slice(0, 6) }))} inputMode="numeric" placeholder="123456" style={{ marginTop: 8, maxWidth: 160 }} /></div><button className="btn primary" disabled={busy === "mfa-verify" || enrollment.code.length !== 6} onClick={verifyEnrollment}>{busy === "mfa-verify" ? "Verifying…" : "Verify"}</button><button className="btn ghost" onClick={() => setEnrollment({ method_type: "email", identifier: "", challenge_id: "", code: "" })}>Cancel</button></div>}
      </div>

      <div style={{ borderTop: "1px solid var(--gray-100)", marginTop: 4, paddingTop: 16 }}>{sessionsPanel}</div>
      <div style={{ borderTop: "1px solid var(--gray-100)", marginTop: 18, paddingTop: 16 }}><div style={{ fontWeight: 700, fontSize: 14, marginBottom: 8 }}>Recent sign-ins</div>{history.length ? <div style={{ display: "grid", gap: 8 }}>{history.slice(0, 8).map((event) => <div key={event.id} className="muted" style={{ fontSize: 12.5, display: "flex", justifyContent: "space-between", gap: 12, flexWrap: "wrap" }}><span>{event.successful ? "Successful sign-in" : "Failed sign-in"} · {event.device_name || event.user_agent || "Unknown device"} · {event.ip_address || "IP unavailable"}</span><span>{dateText(event.created_at)}</span></div>)}</div> : <div className="muted" style={{ fontSize: 12.5 }}>No sign-in history is available yet.</div>}</div>
    </SetCard>
  );
}

function Switch({ defaultOn, onChange, disabled, disabledTitle }) {
  const [on, setOn] = React.useState(!!defaultOn);
  React.useEffect(() => setOn(!!defaultOn), [defaultOn]);
  return (
    <button onClick={() => { if (disabled) return; setOn(!on); onChange && onChange(!on); }} disabled={disabled} title={disabled ? (disabledTitle || "Only a Super Admin can change this") : undefined} style={{ width: 40, height: 22, borderRadius: 999, background: on ? "var(--brand-700)" : "var(--gray-300)", position: "relative", transition: "background .15s ease", cursor: disabled ? "not-allowed" : "pointer", opacity: disabled ? .55 : 1 }}>
      <span style={{ position: "absolute", top: 2, left: on ? 20 : 2, width: 18, height: 18, borderRadius: 999, background: "#fff", transition: "left .15s ease", boxShadow: "0 1px 2px rgba(0,0,0,.2)" }} />
    </button>
  );
}

// ───── Other simple stubs (calendar, assignments, etc.) ─────
function PlaceholderScreen({ title, sub, icon = "construction" }) {
  return (
    <div>
      <Crumbs path={[{ label: title }]} />
      <div className="page-head">
        <div><h1 className="page-title">{title}</h1><p className="page-sub">{sub}</p></div>
      </div>
      <div className="card pad" style={{ textAlign: "center", padding: 80 }}>
        <div className="stat-icon brand" style={{ margin: "0 auto 14px" }}><Icon name={icon} size={22} /></div>
        <h2 style={{ fontFamily: "var(--serif)", fontSize: 24, margin: "0 0 6px" }}>Coming soon</h2>
        <p className="muted" style={{ maxWidth: 360, margin: "0 auto" }}>This surface isn't part of the current scope, but the design + nav slot is reserved.</p>
      </div>
    </div>
  );
}

Object.assign(window, { ReviewQueue, UsersScreen, PermissionsScreen, BulkImportScreen, SettingsScreen, PlaceholderScreen });
