// Vanday DAM — settings (account, users, fields, billing)
function SettingsSidebar({ section, setSection }) {
  const features = (typeof window.useVandayFeatures === "function")
    ? window.useVandayFeatures()
    : null;
  // Only workspace admins may see the Workspace, Account (Usage/Plan/Billing/
  // Branding), and Admin settings. Non-admin members (editor/contributor/
  // viewer) get just their personal settings (Profile, Notifications). Default
  // to admin BEFORE /api/me loads so an admin doesn't see their menu briefly
  // collapse on first paint.
  const isWsAdmin = features ? features.isWorkspaceAdmin === true : true;
  // Beta hides most admin/account surfaces. The settings panel itself stays
  // accessible — just with a much smaller menu. Each item below is gated on
  // a specific feature flag so dev mode shows the full prototype.
  const allItems = [
    { group: "Personal", items: [
      { id: "profile",     name: "Profile",       icon: <IcUsers size={15} />, show: true },
      { id: "notifs",      name: "Notifications", icon: <IcBell size={15} />,  show: !features || features.notifications },
    ]},
    { group: "Workspace", items: [
      { id: "workspace",   name: "Workspace", icon: <IcGrid size={15} />, show: isWsAdmin },
    ]},
    { group: "Account", items: [
      // Usage (workspace stats) stays its own tab. Plan + billing are ONE tab:
      // it shows the current plan + a "paid plans coming soon" teaser until
      // billing is rolled out for this workspace (usageBilling), then the live
      // plan grid + payment/subscription management. (Previously two separate
      // "Plan" and "Billing" tabs — merged because they overlapped.)
      { id: "usage",    name: "Usage",          icon: <IcGrid size={15} />,  show: isWsAdmin },
      { id: "plan",     name: "Plan & billing", icon: <IcCard size={15} />,  show: isWsAdmin },
      { id: "branding", name: "Branding", icon: <IcImage size={15} />, show: isWsAdmin && (!features || features.branding) },
    ]},
    { group: "Admin", items: [
      { id: "users",        name: "Users & permissions", icon: <IcUsers size={15} />, show: isWsAdmin },
      { id: "fields",       name: "Fields",           icon: <IcTag size={15} />,      show: isWsAdmin },
      { id: "workflow",     name: "Workflow labels",  icon: <IcEye size={15} />,      show: isWsAdmin },
      { id: "integrations", name: "Integrations",     icon: <IcLink size={15} />,     show: isWsAdmin /* unified hub: social + storage + design tools, one page */ },
      { id: "api",          name: "API & webhooks",   icon: <IcExternal size={15} />, show: isWsAdmin && (!features || features.apiKeys || features.webhooks) },
      { id: "audit",        name: "Audit log",        icon: <IcEye size={15} />,      show: isWsAdmin && (!features || features.audit) },
      { id: "security",     name: "Content security", icon: <IcShield size={15} />,   show: isWsAdmin && (!features || features.contentSecurity) },
      // Admin-only: toggle paid-tier feature flags per beta account.
      { id: "flags",        name: "Feature flags",    icon: <IcSettings size={15} />, show: features?.isAdmin === true },
    ]},
  ];
  const sections = allItems
    .map((g) => ({ ...g, items: g.items.filter((i) => i.show) }))
    .filter((g) => g.items.length > 0);

  return (
    <aside className="sidebar">
      {sections.map((s) => (
        <React.Fragment key={s.group}>
          <div className="sidebar-header">
            <span className="sidebar-title">{s.group}</span>
          </div>
          <div className="sidebar-section">
            {s.items.map((i) => (
              <button
                key={i.id}
                className={`sidebar-item ${section === i.id ? "active" : ""}`}
                onClick={() => setSection(i.id)}
              >
                <span className="ic">{i.icon}</span>
                {i.name}
              </button>
            ))}
          </div>
        </React.Fragment>
      ))}
      <div className="rail-spacer" />
      <div style={{ padding: "14px 18px", fontSize: 12, color: "var(--text-faint)", borderTop: "1px solid var(--border)" }}>
        Legal notices
      </div>
    </aside>
  );
}

// Settings → Workspace. During the beta every account has a single workspace
// and can't create additional ones — multiple workspaces are a planned
// feature. Member management lives under "Users & permissions". This section
// just sets expectations with a "coming soon" message.
function WorkspaceSection() {
  const me = (typeof window.useVandayMe === "function") ? window.useVandayMe() : null;
  const features = (typeof window.useVandayFeatures === "function") ? window.useVandayFeatures() : null;
  const isWsAdmin = features ? features.isWorkspaceAdmin === true : true;
  const currentName = (me && me.org && me.org.name) || "";

  const [name, setName] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const [saved, setSaved] = React.useState(false);
  const [seeded, setSeeded] = React.useState(false);
  const currentAvatar = (me && me.org && me.org.avatar) || null;
  const [avatarBusy, setAvatarBusy] = React.useState(false);
  const fileInputRef = React.useRef(null);

  // Downscale whatever the admin picks to a 256px square data URL so the stored
  // graphic stays tiny (it rides along in /api/me). Center-crops to square.
  const pickAvatar = async (file) => {
    if (!file) return;
    setAvatarBusy(true);
    try {
      const dataUrl = await new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = () => resolve(reader.result);
        reader.onerror = reject;
        reader.readAsDataURL(file);
      });
      const img = await new Promise((resolve, reject) => {
        const i = new Image();
        i.onload = () => resolve(i);
        i.onerror = reject;
        i.src = dataUrl;
      });
      const S = 256;
      const canvas = document.createElement("canvas");
      canvas.width = S; canvas.height = S;
      const ctx = canvas.getContext("2d");
      const side = Math.min(img.naturalWidth, img.naturalHeight);
      const sx = (img.naturalWidth - side) / 2;
      const sy = (img.naturalHeight - side) / 2;
      ctx.drawImage(img, sx, sy, side, side, 0, 0, S, S);
      const out = canvas.toDataURL("image/png");
      await window.VandayAPI.setOrgAvatar(out);
      if (window.VandayMe) await window.VandayMe.fetchMe();
    } catch (e) {
      const code = (e && e.data && e.data.error) || e.message || "";
      window.alert(
        code.includes("avatar_too_large") ? "That image is too large even after resizing — try a simpler graphic."
          : code.includes("not_admin") ? "Only a workspace admin can change the workspace graphic."
            : "Couldn't update the workspace graphic. Please try a different image.",
      );
    } finally {
      setAvatarBusy(false);
      if (fileInputRef.current) fileInputRef.current.value = "";
    }
  };
  const removeAvatar = async () => {
    setAvatarBusy(true);
    try {
      await window.VandayAPI.setOrgAvatar(null);
      if (window.VandayMe) await window.VandayMe.fetchMe();
    } catch {} finally { setAvatarBusy(false); }
  };

  // Hydrate the field from /api/me once it loads.
  React.useEffect(() => {
    if (currentName && !seeded) { setName(currentName); setSeeded(true); }
  }, [currentName, seeded]);

  const dirty = name.trim().replace(/\s+/g, " ") !== currentName && name.trim() !== "";
  const saveName = async () => {
    const next = name.trim().replace(/\s+/g, " ");
    if (!next || busy) return;
    setBusy(true);
    setSaved(false);
    try {
      await window.VandayAPI.setOrgName(next);
      if (window.VandayMe) await window.VandayMe.fetchMe();
      setSaved(true);
      setTimeout(() => setSaved(false), 2500);
    } catch (e) {
      const code = (e && e.data && e.data.error) || e.message || "";
      window.alert(
        code.includes("name_too_long") ? "That name is too long — keep it under 60 characters."
          : code.includes("bad_name") ? "Please enter a workspace name."
            : code.includes("not_admin") ? "Only a workspace admin can rename the workspace."
              : `Couldn't save the name: ${e.message || "error"}`,
      );
    } finally {
      setBusy(false);
    }
  };

  return (
    <div style={{ maxWidth: 820 }}>
      <h1 className="settings-h1">Workspace</h1>
      <p className="settings-sub" style={{ marginBottom: 20 }}>
        Your workspace name and details.
      </p>

      <div className="section-card" style={{ marginBottom: 18 }}>
        <div style={{ fontWeight: 600, fontSize: 14, marginBottom: 4 }}>Workspace name</div>
        <p className="settings-sub" style={{ margin: "0 0 14px" }}>
          {isWsAdmin
            ? "Shown in the top-left of the app and in the breadcrumb."
            : "The name shown in the top-left of the app. Only a workspace admin can change it."}
        </p>
        <div style={{ display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap" }}>
          <input
            type="text"
            value={name}
            onChange={(e) => setName(e.target.value)}
            maxLength={60}
            disabled={!isWsAdmin || busy}
            placeholder="e.g. Vanday Studio"
            style={{
              flex: 1, minWidth: 240,
              padding: "9px 12px", borderRadius: 8,
              border: "1px solid var(--border)", background: "var(--surface)",
              color: "var(--text)", fontSize: 14, fontFamily: "inherit",
            }}
          />
          {isWsAdmin && (
            <button className="btn accent" disabled={!dirty || busy} onClick={saveName}>
              {busy ? "Saving…" : saved ? "Saved" : "Save"}
            </button>
          )}
        </div>
      </div>

      <div className="section-card" style={{ marginBottom: 18 }}>
        <div style={{ fontWeight: 600, fontSize: 14, marginBottom: 4 }}>Workspace graphic</div>
        <p className="settings-sub" style={{ margin: "0 0 14px" }}>
          {isWsAdmin
            ? "A square logo or image shown next to your workspace name. Square images work best."
            : "The image shown next to your workspace name. Only a workspace admin can change it."}
        </p>
        <div style={{ display: "flex", gap: 16, alignItems: "center", flexWrap: "wrap" }}>
          <div
            style={{
              width: 64, height: 64, borderRadius: 14, flexShrink: 0,
              border: "1px solid var(--border)", overflow: "hidden",
              background: currentAvatar ? "var(--surface)" : "var(--accent, #c8553d)",
              display: "flex", alignItems: "center", justifyContent: "center",
              color: "#fff", fontWeight: 700, fontSize: 24,
            }}
          >
            {currentAvatar
              ? <img src={currentAvatar} alt="" style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
              : (currentName || "W").trim().charAt(0).toUpperCase()}
          </div>
          {isWsAdmin && (
            <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
              <input
                ref={fileInputRef}
                type="file"
                accept="image/png,image/jpeg,image/webp,image/gif"
                style={{ display: "none" }}
                onChange={(e) => pickAvatar(e.target.files && e.target.files[0])}
              />
              <button className="btn" disabled={avatarBusy} onClick={() => fileInputRef.current && fileInputRef.current.click()}>
                {avatarBusy ? "Working…" : currentAvatar ? "Change graphic" : "Upload graphic"}
              </button>
              {currentAvatar && (
                <button className="btn ghost" disabled={avatarBusy} onClick={removeAvatar}>Remove</button>
              )}
            </div>
          )}
        </div>
      </div>

    </div>
  );
}

function clerkErrText(e) {
  if (e && Array.isArray(e.errors) && e.errors[0]) {
    return e.errors[0].longMessage || e.errors[0].message || "Something went wrong.";
  }
  return (e && e.message) || "Something went wrong.";
}

// Profile — a custom, Vanday-styled account panel whose controls write back
// to Clerk's client SDK (window.Clerk.user). Name, avatar, password and
// two-factor all persist for real. Falls back to read-only rows when Clerk
// isn't present (e.g. the static prototype).
function ProfileSection() {
  const [, force] = React.useReducer((x) => x + 1, 0);
  const [hasClerk, setHasClerk] = React.useState(!!window.Clerk);

  // Wait for Clerk to finish loading, then re-render on any user change so
  // the rows reflect saved edits without a refresh.
  React.useEffect(() => {
    if (!window.Clerk) {
      const onReady = () => { setHasClerk(true); force(); };
      window.addEventListener("clerk-ready", onReady);
      return () => window.removeEventListener("clerk-ready", onReady);
    }
    setHasClerk(true);
    let unsub;
    try { unsub = window.Clerk.addListener(() => force()); } catch {}
    return () => { try { unsub && unsub(); } catch {} };
  }, [hasClerk]);

  const user = (window.Clerk && window.Clerk.user) || null;
  const sessionUser = window.VandaySession ? window.VandaySession.getActiveUser() : null;
  const role = sessionUser ? sessionUser.role : null;
  // Deleting the account also tears down the whole workspace, so only a
  // workspace admin may do it. Invited members can't delete a workspace that
  // isn't theirs — an admin removes them instead. Default to true while /api/me
  // loads (solo/personal users are admins of their own workspace).
  const features = (typeof window.useVandayFeatures === "function") ? window.useVandayFeatures() : null;
  const isWsAdmin = features ? features.isWorkspaceAdmin === true : true;

  // ----- editable name -----
  const [editingName, setEditingName] = React.useState(false);
  const [firstName, setFirstName] = React.useState("");
  const [lastName, setLastName] = React.useState("");
  const [nameBusy, setNameBusy] = React.useState(false);
  const startEditName = () => {
    setFirstName(user ? (user.firstName || "") : "");
    setLastName(user ? (user.lastName || "") : "");
    setEditingName(true);
  };
  const saveName = async () => {
    if (!user) return;
    setNameBusy(true);
    try {
      await user.update({ firstName: firstName.trim(), lastName: lastName.trim() });
      setEditingName(false);
    } catch (e) { window.alert(clerkErrText(e)); }
    setNameBusy(false);
  };

  // ----- avatar -----
  const fileRef = React.useRef(null);
  const [avatarBusy, setAvatarBusy] = React.useState(false);
  const onPickPhoto = async (e) => {
    const file = e.target.files && e.target.files[0];
    e.target.value = "";
    if (!file || !user) return;
    setAvatarBusy(true);
    try { await user.setProfileImage({ file }); }
    catch (err) { window.alert(clerkErrText(err)); }
    setAvatarBusy(false);
  };

  // ----- password -----
  const [pwOpen, setPwOpen] = React.useState(false);
  const [curPw, setCurPw] = React.useState("");
  const [newPw, setNewPw] = React.useState("");
  const [confirmPw, setConfirmPw] = React.useState("");
  const [pwBusy, setPwBusy] = React.useState(false);
  const [pwMsg, setPwMsg] = React.useState(null); // { ok, text }
  const passwordEnabled = !!(user && user.passwordEnabled);
  const submitPassword = async () => {
    if (!user) return;
    setPwMsg(null);
    if (newPw.length < 8) { setPwMsg({ ok: false, text: "Use at least 8 characters." }); return; }
    if (newPw !== confirmPw) { setPwMsg({ ok: false, text: "The two passwords don't match." }); return; }
    setPwBusy(true);
    try {
      const params = { newPassword: newPw, signOutOfOtherSessions: false };
      if (passwordEnabled) params.currentPassword = curPw;
      await user.updatePassword(params);
      setPwMsg({ ok: true, text: passwordEnabled ? "Password updated." : "Password set." });
      setCurPw(""); setNewPw(""); setConfirmPw("");
      setTimeout(() => { setPwOpen(false); setPwMsg(null); }, 1200);
    } catch (e) { setPwMsg({ ok: false, text: clerkErrText(e) }); }
    setPwBusy(false);
  };

  // Display values.
  const fullName = user ? (user.fullName || [user.firstName, user.lastName].filter(Boolean).join(" ") || "—")
    : (sessionUser ? sessionUser.name : "—");
  const email = user ? (user.primaryEmailAddress && user.primaryEmailAddress.emailAddress) || "—"
    : (sessionUser ? sessionUser.email : "—");
  const initials = (fullName && fullName !== "—")
    ? fullName.split(/[\s._-]+/).filter(Boolean).slice(0, 2).map((s) => s[0].toUpperCase()).join("")
    : "?";
  const hasImage = !!(user && user.hasImage && user.imageUrl);

  const inputStyle = {
    width: "100%", background: "var(--surface)", border: "1px solid var(--border)",
    borderRadius: 8, padding: "9px 11px", fontSize: 13, outline: "none",
  };

  // ----- delete account (destructive, irreversible) -----
  const [delOpen, setDelOpen] = React.useState(false);
  const [delText, setDelText] = React.useState("");
  const [delBusy, setDelBusy] = React.useState(false);
  const [delErr, setDelErr] = React.useState(null);
  const delReady = delText.trim().toUpperCase() === "DELETE";
  const confirmDelete = async () => {
    if (!delReady) return;
    setDelBusy(true); setDelErr(null);
    try {
      await window.VandayAPI.deleteAccount();
      // Everything is gone server-side, including the Clerk login. Sign out
      // locally and bounce to the home/sign-in screen.
      try { if (window.Clerk) await window.Clerk.signOut(); } catch {}
      window.location.href = "/";
    } catch (e) {
      setDelErr((e && e.message) || "Could not delete your account. Please try again.");
      setDelBusy(false);
    }
  };

  return (
    <div style={{ maxWidth: 820 }}>
      <h1 className="settings-h1">Profile</h1>
      <p className="settings-sub">Your account details — changes save to your sign-in.</p>

      <div className="section-card">
        {/* Avatar */}
        <div style={{ display: "flex", gap: 22, alignItems: "center", marginBottom: 22 }}>
          <div style={{
            width: 64, height: 64, borderRadius: 16, overflow: "hidden",
            background: (sessionUser && sessionUser.color) || "var(--accent)", color: "white",
            display: "grid", placeItems: "center", fontWeight: 600, fontSize: 22,
            backgroundImage: hasImage ? `url(${user.imageUrl})` : undefined,
            backgroundSize: "cover", backgroundPosition: "center",
          }}>{hasImage ? "" : initials}</div>
          <div>
            <input ref={fileRef} type="file" accept="image/png,image/jpeg" style={{ display: "none" }} onChange={onPickPhoto} />
            <button className="btn" disabled={!user || avatarBusy} onClick={() => fileRef.current && fileRef.current.click()}>
              {avatarBusy ? "Uploading…" : "Upload photo"}
            </button>
            <div style={{ fontSize: 12, color: "var(--text-faint)", marginTop: 6 }}>PNG or JPG, up to 5 MB.</div>
          </div>
        </div>

        {/* Full name */}
        <div className="row">
          <div>
            <div className="row-label">Full name</div>
            <div className="row-sub">Shown to teammates on comments and activity.</div>
          </div>
          {editingName ? (
            <div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap", justifyContent: "flex-end" }}>
              <input style={{ ...inputStyle, width: 130 }} placeholder="First" value={firstName} onChange={(e) => setFirstName(e.target.value)} />
              <input style={{ ...inputStyle, width: 130 }} placeholder="Last" value={lastName} onChange={(e) => setLastName(e.target.value)} />
              <button className="btn sm" onClick={() => setEditingName(false)} disabled={nameBusy}>Cancel</button>
              <button className="btn accent sm" onClick={saveName} disabled={nameBusy}>{nameBusy ? "Saving…" : "Save"}</button>
            </div>
          ) : (
            <div style={{ display: "flex", gap: 12, alignItems: "center" }}>
              <span className="row-value">{fullName}</span>
              {user && <button className="btn sm" onClick={startEditName}>Edit</button>}
            </div>
          )}
        </div>

        {/* Email */}
        <div className="row">
          <div>
            <div className="row-label">Email</div>
            <div className="row-sub">Used for sign-in and notifications.</div>
          </div>
          <div className="row-value">{email}</div>
        </div>

        {/* Role */}
        {role && (
          <div className="row">
            <div>
              <div className="row-label">Role</div>
              <div className="row-sub">Your workspace permissions.</div>
            </div>
            <div className="row-value">{role}</div>
          </div>
        )}

        {/* Password */}
        <div className="row" style={{ alignItems: pwOpen ? "flex-start" : "center" }}>
          <div>
            <div className="row-label">Password</div>
            <div className="row-sub">{passwordEnabled ? "Change the password you sign in with." : "No password set — you sign in with a connected account. Add one as a backup."}</div>
          </div>
          {!pwOpen ? (
            <button className="btn sm" disabled={!user} onClick={() => { setPwOpen(true); setPwMsg(null); }}>
              {passwordEnabled ? "Change" : "Set password"}
            </button>
          ) : (
            <div style={{ display: "flex", flexDirection: "column", gap: 8, width: 280 }}>
              {passwordEnabled && (
                <input type="password" autoComplete="current-password" style={inputStyle} placeholder="Current password" value={curPw} onChange={(e) => setCurPw(e.target.value)} />
              )}
              <input type="password" autoComplete="new-password" style={inputStyle} placeholder="New password" value={newPw} onChange={(e) => setNewPw(e.target.value)} />
              <input type="password" autoComplete="new-password" style={inputStyle} placeholder="Confirm new password" value={confirmPw} onChange={(e) => setConfirmPw(e.target.value)} />
              {pwMsg && <div style={{ fontSize: 12, color: pwMsg.ok ? "var(--ok, #2e7d52)" : "var(--accent, #c8553d)" }}>{pwMsg.text}</div>}
              <div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
                <button className="btn sm" onClick={() => { setPwOpen(false); setPwMsg(null); setCurPw(""); setNewPw(""); setConfirmPw(""); }} disabled={pwBusy}>Cancel</button>
                <button className="btn accent sm" onClick={submitPassword} disabled={pwBusy}>{pwBusy ? "Saving…" : "Save password"}</button>
              </div>
            </div>
          )}
        </div>
      </div>

      {/* Danger zone — permanent account + workspace deletion. Admins only:
          a non-admin member can't tear down a workspace they don't own. */}
      {isWsAdmin && (
      <div
        className="section-card"
        style={{ marginTop: 18, borderColor: "color-mix(in oklch, var(--accent, #c8553d) 35%, var(--border))" }}
      >
        <div className="row" style={{ borderBottom: "none" }}>
          <div>
            <div className="row-label" style={{ color: "var(--accent, #c8553d)" }}>Delete account</div>
            <div className="row-sub" style={{ maxWidth: 460 }}>
              Permanently delete your account and workspace — every image, crop,
              folder and share, plus any teammates' access. This cannot be
              undone and nothing can be recovered.
            </div>
          </div>
          <button
            className="btn sm"
            style={{
              borderColor: "var(--accent, #c8553d)",
              color: "var(--accent, #c8553d)",
              fontWeight: 600,
            }}
            onClick={() => { setDelOpen(true); setDelText(""); setDelErr(null); }}
          >
            Delete account
          </button>
        </div>
      </div>
      )}

      {delOpen && (
        <div
          role="dialog"
          aria-modal="true"
          aria-labelledby="vanday-delete-title"
          style={{
            position: "fixed", inset: 0, zIndex: 10000,
            background: "rgba(8, 12, 22, 0.55)",
            display: "grid", placeItems: "center", padding: 24,
            backdropFilter: "blur(4px)",
          }}
          onClick={(e) => { if (e.target === e.currentTarget && !delBusy) setDelOpen(false); }}
        >
          <div style={{
            background: "var(--surface, #fff)", color: "var(--text, #111)",
            width: "100%", maxWidth: 460, borderRadius: 14,
            boxShadow: "0 20px 60px rgba(0,0,0,0.25)", padding: "26px 26px 22px",
          }}>
            <div id="vanday-delete-title" style={{ fontSize: 17, fontWeight: 700, marginBottom: 8, color: "var(--accent, #c8553d)" }}>
              Delete your account?
            </div>
            <p style={{ fontSize: 13.5, lineHeight: 1.55, color: "var(--text-muted)", margin: "0 0 16px" }}>
              This permanently deletes your account and workspace — every image,
              crop, folder and share, and it removes any teammates you've invited.
              <strong> Everything will be gone and cannot be recovered.</strong>
            </p>
            <label style={{ fontSize: 12.5, fontWeight: 600, display: "block", marginBottom: 6 }}>
              Type <span style={{ fontFamily: "var(--font-mono)" }}>DELETE</span> to confirm
            </label>
            <input
              autoFocus
              value={delText}
              onChange={(e) => setDelText(e.target.value)}
              onKeyDown={(e) => { if (e.key === "Enter" && delReady && !delBusy) confirmDelete(); }}
              placeholder="DELETE"
              style={{
                width: "100%", background: "var(--surface)", border: "1px solid var(--border)",
                borderRadius: 8, padding: "9px 11px", fontSize: 13, outline: "none",
                fontFamily: "var(--font-mono)", letterSpacing: "0.06em",
              }}
            />
            {delErr && (
              <div style={{ fontSize: 12, color: "var(--accent, #c8553d)", marginTop: 8 }}>{delErr}</div>
            )}
            <div style={{ display: "flex", gap: 10, justifyContent: "flex-end", marginTop: 18 }}>
              <button className="btn" onClick={() => setDelOpen(false)} disabled={delBusy}>Cancel</button>
              <button
                className="btn"
                onClick={confirmDelete}
                disabled={!delReady || delBusy}
                style={{
                  background: delReady ? "var(--accent, #c8553d)" : "var(--surface-sunken)",
                  color: delReady ? "white" : "var(--text-faint)",
                  border: "none", fontWeight: 600,
                  cursor: delReady && !delBusy ? "pointer" : "not-allowed",
                }}
              >
                {delBusy ? "Deleting…" : "Permanently delete"}
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// Format a millisecond timestamp as a short relative time ("3d ago").
function relTime(ms) {
  if (!ms) return "—";
  const s = Math.max(0, (Date.now() - ms) / 1000);
  if (s < 60) return "just now";
  if (s < 3600) return `${Math.floor(s / 60)}m ago`;
  if (s < 86400) return `${Math.floor(s / 3600)}h ago`;
  if (s < 86400 * 30) return `${Math.floor(s / 86400)}d ago`;
  return new Date(ms).toLocaleDateString();
}

// Admin permissions manager. Fetches real workspace access from the server:
// the member roster with per-user roles, plus the company-domain auto-join
// rule. Each member's role can be changed inline (persists via PATCH); the
// domain rule is configured below (persists via PUT). Non-admins see a
// read-only view.
function UsersSection() {
  const [data, setData] = React.useState(null);
  const [err, setErr] = React.useState(null);
  const [busyId, setBusyId] = React.useState(null);

  // Beta seat cap (owner + invites combined). Read from the server so the UI
  // matches what's actually enforced on POST /api/org/invites.
  const limits = (typeof window.useVandayLimits === "function") ? window.useVandayLimits() : null;
  const seatCap = limits?.maxSeats ?? 3;
  // Whether to surface upgrade CTAs (only when billing is actually available to
  // this workspace — i.e. the usageBilling flag is on). Called unconditionally
  // here, before the early returns below, to respect the Rules of Hooks.
  const features = (typeof window.useVandayFeatures === "function") ? window.useVandayFeatures() : null;

  // Domain-rule form state, hydrated from the server on load.
  const [drEnabled, setDrEnabled] = React.useState(false);
  const [drDomain, setDrDomain] = React.useState("");
  const [drRole, setDrRole] = React.useState("viewer");
  const [drBusy, setDrBusy] = React.useState(false);
  const [drSaved, setDrSaved] = React.useState(false);

  // Invite form state.
  const [invEmail, setInvEmail] = React.useState("");
  const [invRole, setInvRole] = React.useState("editor");
  const [invBusy, setInvBusy] = React.useState(false);
  const [invNote, setInvNote] = React.useState(null); // { ok, text }

  const load = React.useCallback(() => {
    window.VandayAPI.getOrgAccess()
      .then((d) => {
        setData(d);
        const r = d.domainRule || {};
        setDrEnabled(!!r.enabled);
        setDrDomain(r.domain || d.suggestedDomain || "");
        setDrRole(r.role || "viewer");
      })
      .catch((e) => setErr(e.message || "Failed to load"));
  }, []);
  React.useEffect(() => { load(); }, [load]);

  if (err) {
    return (
      <>
        <h1 className="settings-h1">Users &amp; permissions</h1>
        <p className="settings-sub" style={{ color: "var(--danger, oklch(0.6 0.18 25))" }}>Couldn't load workspace access: {err}</p>
      </>
    );
  }
  if (!data) {
    return (
      <>
        <h1 className="settings-h1">Users &amp; permissions</h1>
        <p className="settings-sub">Loading…</p>
      </>
    );
  }

  const { myRole, isAdmin, isPersonal, roles, members } = data;
  const invites = data.invites || [];
  // Seats in use = billable (non-viewer) members + outstanding billable invites.
  // Viewers (view + download only) are unlimited and never count toward the cap.
  const billableMembers = members.filter((m) => m.role !== "viewer").length;
  const billablePending = invites.filter((iv) => !iv.acceptedAt && iv.role !== "viewer").length;
  const viewerCount = members.filter((m) => m.role === "viewer").length;
  const seatsUsed = billableMembers + billablePending;
  const seatsFull = seatsUsed >= seatCap;
  const roleOptions = roles.map((r) => ({ value: r.id, label: r.label }));
  // At the cap you can still add viewers (unlimited) — only billable invites blocked.
  const inviteBlocked = seatsFull && invRole !== "viewer";
  // Show the upgrade path only to admins who actually have billing enabled.
  const canUpgrade = isAdmin && features?.usageBilling === true;
  const goToPlan = () => window.dispatchEvent(new CustomEvent("vanday-nav-settings", { detail: "plan" }));
  // Company-domain auto-join is a Pro-plan feature (server enforces it too).
  const canUseDomainAccess = limits?.companyDomainAccess === true;
  const roleLabel = (id) => (roles.find((r) => r.id === id) || {}).label || id;

  const sendInvite = async () => {
    const email = invEmail.trim().toLowerCase();
    if (!email || invBusy) return;
    setInvBusy(true);
    setInvNote(null);
    try {
      const res = await window.VandayAPI.inviteUser(email, invRole);
      setData((d) => ({ ...d, invites: [res.invite, ...(d.invites || []).filter((i) => i.id !== res.invite.id)] }));
      setInvEmail("");
      setInvNote(res.emailSent
        ? { ok: true, text: `Invite email sent to ${email}.` }
        // Resend isn't set up on every environment (e.g. dev). The user
        // doesn't need to know the env-var name — they only need to know
        // that the invite IS recorded and the person can still join.
        : { ok: true, text: `${email} added to pending invites. (Heads-up: the invite email couldn't be sent right now — they can still join by signing up with this email.)` });
    } catch (e) {
      const code = (e && e.data && e.data.error) || e.message || "";
      // Seat cap with upgrade hint → pop the global paywall modal so the
      // admin can convert to the next plan (or start a trial) right from
      // here instead of having to navigate to Billing manually.
      if (code.includes("seat_limit_reached") && e.data?.upgradeTo) {
        window.openVandayPaywall?.({
          title: `Add more teammates with ${e.data.upgradeTo === "growth" ? "Growth" : e.data.upgradeTo === "pro" ? "Pro" : "a larger plan"}`,
          plan: e.data.plan,
          upgradeTo: e.data.upgradeTo,
          message: e.data.message,
        });
        return;
      }
      window.alert(
        code.includes("seat_limit_reached") ? (e.data && e.data.message) || `Your workspace is limited to ${seatCap} people.`
          : code.includes("already_member") ? "That person is already in this workspace."
            : code.includes("bad_email") ? "That doesn't look like a valid email address."
              : `Couldn't send the invite: ${e.message || "error"}`,
      );
    } finally {
      setInvBusy(false);
    }
  };
  const revokeInvite = async (id) => {
    try {
      await window.VandayAPI.revokeInvite(id);
      setData((d) => ({ ...d, invites: (d.invites || []).filter((i) => i.id !== id) }));
    } catch (e) {
      window.alert(`Couldn't revoke: ${e.message || "error"}`);
    }
  };

  const changeRole = async (userId, role) => {
    setBusyId(userId);
    try {
      await window.VandayAPI.setMemberRole(userId, role);
      setData((d) => ({ ...d, members: d.members.map((m) => m.id === userId ? { ...m, role } : m) }));
    } catch (e) {
      window.alert(
        e.message && e.message.includes("last_admin")
          ? "You can't remove the last admin — promote someone else first."
          : e.message && e.message.includes("owner_must_stay_admin")
            ? "The workspace owner must stay an admin."
            : `Couldn't change role: ${e.message || "error"}`,
      );
    } finally {
      setBusyId(null);
    }
  };

  const removeMember = async (m) => {
    const fullName = (m.name || "").trim();
    const who = fullName || (m.email || "").split("@")[0] || "this person";
    if (!window.confirm(`Remove ${who} from the workspace? They'll lose access immediately.`)) return;
    setBusyId(m.id);
    try {
      await window.VandayAPI.removeMember(m.id);
      setData((d) => ({ ...d, members: d.members.filter((x) => x.id !== m.id) }));
    } catch (e) {
      const code = (e && e.data && e.data.error) || e.message || "";
      window.alert(
        code.includes("cannot_remove_admin")
          ? "Admins can't remove each other. Change their role to something other than admin first."
          : code.includes("cannot_remove_owner")
            ? "The workspace owner can't be removed."
            : `Couldn't remove this person: ${e.message || "error"}`,
      );
    } finally {
      setBusyId(null);
    }
  };

  const saveDomainRule = async () => {
    setDrBusy(true);
    setDrSaved(false);
    try {
      const res = await window.VandayAPI.setDomainRule({ enabled: drEnabled, domain: drDomain.trim(), role: drRole });
      const r = res.domainRule || {};
      setDrEnabled(!!r.enabled);
      if (r.domain) setDrDomain(r.domain);
      if (r.role) setDrRole(r.role);
      setDrSaved(true);
      setTimeout(() => setDrSaved(false), 2500);
    } catch (e) {
      const code = (e && e.data && e.data.error) || e.message || "";
      window.alert(
        code.includes("requires_pro")
          ? "Company-domain access is available on the Pro plan. Upgrade to enable it."
          : code.includes("generic_domain")
            ? "Generic email domains like gmail.com can't be used — they'd let anyone with that provider join. Enter your company's own domain (e.g. acme.com)."
            : code.includes("bad_domain")
              ? "That doesn't look like a valid domain (e.g. acme.com)."
              : `Couldn't save the rule: ${e.message || "error"}`,
      );
    } finally {
      setDrBusy(false);
    }
  };

  return (
    <>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 20, marginBottom: 28 }}>
        <div>
          <h1 className="settings-h1">Users &amp; permissions</h1>
          <p className="settings-sub" style={{ marginBottom: 0 }}>
            {isAdmin
              ? "Give each person a role that decides what they can do across the workspace."
              : "View who has access to this workspace."}
          </p>
        </div>
      </div>

      {isPersonal && (
        <div className="section-card" style={{ display: "flex", gap: 10, alignItems: "flex-start" }}>
          <IcInfo size={16} />
          <div style={{ fontSize: 13, color: "var(--text-muted)" }}>
            {canUseDomainAccess
              ? "This is your personal workspace. Turn on the company-domain rule below to let teammates join automatically when they sign up with your company email."
              : "This is your personal workspace. Invite teammates by email below to give them access."}
          </div>
        </div>
      )}

      {billableMembers > seatCap && (
        <div style={{
          display: "flex", alignItems: "flex-start", gap: 10, padding: "12px 14px",
          background: "oklch(0.98 0.04 50)", border: "1px solid oklch(0.88 0.08 50)",
          borderRadius: 8, marginBottom: 12, fontSize: 13, lineHeight: 1.5,
        }}>
          <span style={{ fontSize: 15, flexShrink: 0, marginTop: 1 }}>⚠️</span>
          <span>
            <strong>Your workspace is over its seat limit.</strong>{" "}
            You have {billableMembers} of {seatCap} seats filled by editors and admins (viewers don't count).
            New editors can't join until you're under the limit.
            {isAdmin && (
              <>{" "}Remove members below or{" "}
                <button
                  onClick={() => window.dispatchEvent(new CustomEvent("vanday-nav-settings", { detail: "plan" }))}
                  style={{ background: "none", border: "none", padding: 0, cursor: "pointer", fontWeight: 600, textDecoration: "underline", fontSize: "inherit", color: "inherit" }}
                >upgrade your plan</button>.
              </>
            )}
          </span>
        </div>
      )}

      <div className="section-card" style={{ padding: 0, overflow: "hidden" }}>
        <div style={{ padding: "14px 16px", display: "flex", alignItems: "center", gap: 8, borderBottom: "1px solid var(--border)" }}>
          <span style={{ fontWeight: 600, fontSize: 14 }}>People</span>
          <span style={{ marginLeft: "auto", fontSize: 12, color: "var(--text-muted)" }}>
            {members.length} {members.length === 1 ? "person" : "people"}
          </span>
        </div>
        <table className="table">
          <thead>
            <tr>
              <th>User</th>
              <th>Email</th>
              <th>Role</th>
              <th>Last active</th>
              {isAdmin && <th></th>}
            </tr>
          </thead>
          <tbody>
            {members.map((m) => {
              // Prefer the person's real name; fall back to the email's local
              // part. Never surface the opaque user id.
              const fullName = (m.name || "").trim();
              const emailLocal = (m.email || "").split("@")[0];
              const display = fullName || emailLocal || "Team member";
              const initials = (
                fullName
                  ? fullName.split(/\s+/).map((w) => w[0]).slice(0, 2).join("")
                  : display.slice(0, 2)
              ).toUpperCase();
              return (
                <tr key={m.id}>
                  <td>
                    <span className="pill-user" style={{ background: "transparent", padding: 0 }}>
                      <span className="av" style={{ width: 24, height: 24, fontSize: 11 }}>{initials}</span>
                      <span style={{ marginLeft: 8 }}>
                        {display}
                        {m.isSelf && <span style={{ color: "var(--text-faint)", fontWeight: 400 }}> (you)</span>}
                        {m.isOwner && <span className="role-pill" style={{ marginLeft: 6 }}>Owner</span>}
                      </span>
                    </span>
                  </td>
                  <td style={{ color: "var(--text-muted)", fontFamily: "var(--font-mono)", fontSize: 12 }}>{m.email}</td>
                  <td>
                    {isAdmin && !m.isOwner ? (
                      <RoleSelect
                        value={m.role}
                        disabled={busyId === m.id}
                        options={roleOptions}
                        onChange={(r) => changeRole(m.id, r)}
                      />
                    ) : (
                      <span className="role-pill">{roleLabel(m.role)}</span>
                    )}
                  </td>
                  <td style={{ color: "var(--text-muted)" }}>{relTime(m.lastSeenAt)}</td>
                  {isAdmin && (
                    <td style={{ textAlign: "right" }}>
                      {!m.isOwner && !m.isSelf && m.role !== "admin" && (
                        <button
                          type="button"
                          className="btn ghost sm"
                          disabled={busyId === m.id}
                          onClick={() => removeMember(m)}
                          style={{ color: "var(--accent, #c8553d)" }}
                        >
                          Remove
                        </button>
                      )}
                    </td>
                  )}
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>

      {/* Invite people */}
      {isAdmin && (
        <div className="section-card">
          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, flexWrap: "wrap" }}>
            <div style={{ fontWeight: 600, fontSize: 14 }}>Invite people</div>
            <span style={{
              fontSize: 12, fontWeight: 600,
              color: seatsFull ? "var(--accent, #c8553d)" : "var(--text-muted)",
              background: "var(--surface-sunken)", border: "1px solid var(--border)",
              borderRadius: 999, padding: "3px 10px", whiteSpace: "nowrap",
            }}>
              {seatsUsed} of {seatCap} seats used{viewerCount ? ` · ${viewerCount} viewer${viewerCount === 1 ? "" : "s"} free` : ""}
            </span>
          </div>
          <p className="settings-sub" style={{ margin: "2px 0 0" }}>
            {seatsFull
              ? (canUpgrade
                  ? `You've filled all ${seatCap} editor seats. Viewers (view-only) are still unlimited — pick the Viewer role to keep adding people, or upgrade for more editor seats.`
                  : `You've filled all ${seatCap} editor seats. Viewers (view-only) are still unlimited — pick the Viewer role, or remove a member to free a seat.`)
              : "Invite a teammate by email and pick their role. They'll join automatically the first time they sign in with that email. Viewers (view + download only) are unlimited and don't use a seat."}
          </p>
          {seatsFull && canUpgrade && (
            <button className="btn accent" style={{ marginTop: 12 }} onClick={goToPlan}>
              <IcSparkles size={14} /> Upgrade plan
            </button>
          )}
          <div style={{ display: "flex", gap: 12, alignItems: "flex-end", marginTop: 14, flexWrap: "wrap" }}>
            <div style={{ display: "grid", gap: 4, flex: 1, minWidth: 220 }}>
              <label style={{ fontSize: 12, color: "var(--text-muted)" }}>Email address</label>
              <div className="search" style={{ width: "100%" }}>
                <input
                  type="email"
                  placeholder="teammate@company.com"
                  value={invEmail}
                  disabled={invBusy || inviteBlocked}
                  onChange={(e) => setInvEmail(e.target.value)}
                  onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); sendInvite(); } }}
                />
              </div>
            </div>
            <div style={{ display: "grid", gap: 4 }}>
              <label style={{ fontSize: 12, color: "var(--text-muted)" }}>Role</label>
              {/* Role stays selectable even at the cap so you can switch to Viewer. */}
              <RoleSelect value={invRole} disabled={invBusy} options={roleOptions} onChange={setInvRole} />
            </div>
            <button className="btn accent" disabled={!invEmail.trim() || invBusy || inviteBlocked} onClick={sendInvite}>
              <IcPlus size={14} /> {invBusy ? "Inviting…" : "Send invite"}
            </button>
          </div>

          {invNote && (
            <div style={{
              marginTop: 12, fontSize: 13, lineHeight: 1.5,
              color: invNote.ok ? "oklch(0.5 0.13 150)" : "var(--text-muted)",
            }}>
              {invNote.ok ? "✓ " : "⚠ "}{invNote.text}
            </div>
          )}

          {invites.length > 0 && (
            <div style={{ marginTop: 16 }}>
              <div style={{ fontSize: 12, textTransform: "uppercase", letterSpacing: "0.06em", color: "var(--text-faint)", fontWeight: 500, marginBottom: 8 }}>
                Pending invites
              </div>
              <div style={{ display: "grid", gap: 6 }}>
                {invites.map((iv) => (
                  <div key={iv.id} style={{ display: "flex", alignItems: "center", gap: 10, padding: "8px 12px", border: "1px solid var(--border)", borderRadius: 8 }}>
                    <IcClock size={14} />
                    <span style={{ fontFamily: "var(--font-mono)", fontSize: 13 }}>{iv.email}</span>
                    <span className="role-pill">{roleLabel(iv.role)}</span>
                    <span style={{ marginLeft: "auto", fontSize: 12, color: "var(--text-faint)" }}>invited {relTime(iv.createdAt)}</span>
                    <button className="btn ghost sm" onClick={() => revokeInvite(iv.id)} title="Revoke invite"><IcClose size={14} /></button>
                  </div>
                ))}
              </div>
            </div>
          )}
        </div>
      )}

      {/* Role legend */}
      <div className="section-card">
        <div style={{ fontWeight: 600, fontSize: 13, marginBottom: 8 }}>What each role can do</div>
        <div style={{ display: "grid", gap: 6 }}>
          {roles.map((r) => (
            <div key={r.id} style={{ display: "flex", gap: 8, fontSize: 13 }}>
              <span className="role-pill" style={{ minWidth: 92, textAlign: "center" }}>{r.label}</span>
              <span style={{ color: "var(--text-muted)" }}>{r.desc}</span>
            </div>
          ))}
        </div>
      </div>

      {/* Company-domain auto-join rule — Pro-only; hidden entirely off-plan. */}
      {canUseDomainAccess && (
      <div className="section-card">
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 12 }}>
          <div>
            <div style={{ fontWeight: 600, fontSize: 14 }}>Company-domain access</div>
            <p className="settings-sub" style={{ margin: "2px 0 0" }}>
              Automatically add anyone who signs up with your company email domain, and give them a starting role. Must be a domain you own — generic providers like gmail.com aren't allowed.
            </p>
          </div>
          <label style={{ display: "inline-flex", alignItems: "center", gap: 8, fontSize: 13, whiteSpace: "nowrap" }}>
            <input
              type="checkbox"
              checked={drEnabled}
              disabled={!isAdmin || drBusy}
              onChange={(e) => setDrEnabled(e.target.checked)}
            />
            Enabled
          </label>
        </div>

        <div style={{ display: "flex", gap: 12, alignItems: "flex-end", marginTop: 14, flexWrap: "wrap", opacity: drEnabled ? 1 : 0.55 }}>
          <div style={{ display: "grid", gap: 4 }}>
            <label style={{ fontSize: 12, color: "var(--text-muted)" }}>Email domain</label>
            <div className="search" style={{ width: 220 }}>
              <span style={{ color: "var(--text-faint)" }}>@</span>
              <input
                placeholder="acme.com"
                value={drDomain}
                disabled={!isAdmin || !drEnabled || drBusy}
                onChange={(e) => setDrDomain(e.target.value)}
              />
            </div>
          </div>
          <div style={{ display: "grid", gap: 4 }}>
            <label style={{ fontSize: 12, color: "var(--text-muted)" }}>Default role</label>
            <RoleSelect
              value={drRole}
              disabled={!isAdmin || !drEnabled || drBusy}
              options={roleOptions}
              onChange={setDrRole}
            />
          </div>
          {isAdmin && (
            <button className="btn" disabled={drBusy} onClick={saveDomainRule}>
              {drBusy ? "Saving…" : drSaved ? "Saved ✓" : "Save rule"}
            </button>
          )}
        </div>
        {!isAdmin && (
          <p className="settings-sub" style={{ marginTop: 10 }}>Only admins can change workspace access.</p>
        )}
      </div>
      )}
    </>
  );
}

// New-field modal — pick a label, type and category for a custom field.
function NewFieldModal({ types, categories, onCreate, onClose }) {
  const [label, setLabel] = React.useState("");
  const [type, setType] = React.useState((types && types[0]) || "Text");
  const [category, setCategory] = React.useState("Custom");
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState(null);
  const inputRef = React.useRef(null);
  React.useEffect(() => { inputRef.current && inputRef.current.focus(); }, []);
  React.useEffect(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKey);
    document.body.style.overflow = "hidden";
    return () => { window.removeEventListener("keydown", onKey); document.body.style.overflow = ""; };
  }, [onClose]);

  const submit = async () => {
    const l = label.trim();
    if (!l || busy) return;
    setBusy(true); setError(null);
    try { await onCreate({ label: l, type, category }); }
    catch (e) { setError(e.message || "Could not create field."); setBusy(false); }
  };

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal nf-modal" onClick={(e) => e.stopPropagation()}>
        <header className="nf-head">
          <div>
            <h2 className="nf-title">New field</h2>
            <p className="nf-sub">Add a custom metadata field for your assets.</p>
          </div>
          <button className="btn ghost sm" onClick={onClose} title="Close"><IcClose size={16} /></button>
        </header>

        <div className="nf-body">
          <div className="nf-block">
            <label className="nf-label" htmlFor="nf-name">Field name</label>
            <input
              id="nf-name"
              ref={inputRef}
              className="nf-input"
              placeholder="e.g. Campaign"
              value={label}
              onChange={(e) => setLabel(e.target.value)}
              onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); submit(); } }}
            />
          </div>

          <div className="nf-grid">
            <div className="nf-block">
              <label className="nf-label" htmlFor="nf-type">Type</label>
              <select id="nf-type" className="nf-select" value={type} onChange={(e) => setType(e.target.value)}>
                {(types || []).map((t) => <option key={t} value={t}>{t}</option>)}
              </select>
            </div>
            <div className="nf-block">
              <label className="nf-label" htmlFor="nf-cat">Category</label>
              <select id="nf-cat" className="nf-select" value={category} onChange={(e) => setCategory(e.target.value)}>
                {(categories || []).map((c) => <option key={c} value={c}>{c}</option>)}
              </select>
            </div>
          </div>

          <div className="nf-hint">
            <IcSparkles className="ic" size={14} />
            Custom fields show up on every asset and are fully searchable.
          </div>

          {error && <div style={{ fontSize: 12.5, color: "var(--danger, oklch(0.6 0.18 25))" }}>{error}</div>}
        </div>

        <footer className="nf-foot">
          <button className="btn" onClick={onClose}>Cancel</button>
          <button className="btn accent" onClick={submit} disabled={!label.trim() || busy}>
            <IcPlus size={14} /> {busy ? "Adding…" : "Add field"}
          </button>
        </footer>
      </div>
    </div>
  );
}

function FieldsSection() {
  const [fields, setFields] = React.useState(null);
  const [meta, setMeta] = React.useState({ types: [], categories: [] });
  const [err, setErr] = React.useState(null);
  const [busyKey, setBusyKey] = React.useState(null);
  const [showNew, setShowNew] = React.useState(false);

  const load = React.useCallback(() => {
    window.VandayAPI.listFields()
      .then((d) => { setFields(d.fields || []); setMeta({ types: d.types || [], categories: d.categories || [] }); })
      .catch((e) => setErr(e.message || "Failed to load"));
  }, []);
  React.useEffect(() => { load(); }, [load]);

  const toggleVisible = async (f) => {
    setBusyKey(f.key);
    const next = !f.visible;
    setFields((fs) => fs.map((x) => x.key === f.key ? { ...x, visible: next } : x));
    try { await window.VandayAPI.setFieldVisible(f.key, next); }
    catch (e) {
      setFields((fs) => fs.map((x) => x.key === f.key ? { ...x, visible: !next } : x));
      window.alert(`Couldn't update: ${e.message || "error"}`);
    } finally { setBusyKey(null); }
  };
  const removeField = async (f) => {
    if (!window.confirm(`Remove the "${f.label}" field?`)) return;
    setFields((fs) => fs.filter((x) => x.key !== f.key));
    try { await window.VandayAPI.deleteField(f.key); }
    catch (e) { window.alert(`Couldn't remove: ${e.message || "error"}`); load(); }
  };
  const createField = async ({ label, type, category }) => {
    const res = await window.VandayAPI.createField({ label, type, category });
    setFields((fs) => [...(fs || []), res.field]);
    setShowNew(false);
  };

  if (err) {
    return (<><h1 className="settings-h1">Fields</h1>
      <p className="settings-sub" style={{ color: "var(--danger, oklch(0.6 0.18 25))" }}>Couldn't load fields: {err}</p></>);
  }
  if (!fields) {
    return (<><h1 className="settings-h1">Fields</h1><p className="settings-sub">Loading…</p></>);
  }

  // Preserve the server order but group by category for display.
  const order = [];
  const grouped = {};
  for (const f of fields) {
    if (!grouped[f.category]) { grouped[f.category] = []; order.push(f.category); }
    grouped[f.category].push(f);
  }

  return (
    <>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 20, marginBottom: 28 }}>
        <div>
          <h1 className="settings-h1">Fields</h1>
          <p className="settings-sub" style={{ marginBottom: 0 }}>Define the metadata captured for every asset. AI fields auto-populate on upload.</p>
        </div>
        <button className="btn accent" onClick={() => setShowNew(true)}><IcPlus size={14} /> New field</button>
      </div>

      {order.map((cat) => (
        <div className="section-card" key={cat}>
          <h2 className="section-h" style={{ display: "flex", alignItems: "center", gap: 8 }}>
            {cat}
            {cat === "Auto-generated" && (
              <span className="tag ai">
                <IcSparkles size={9} style={{ verticalAlign: "middle", marginRight: 3 }} />
                AI
              </span>
            )}
          </h2>
          {grouped[cat].map((f) => (
            <div className="row" key={f.key}>
              <div>
                <div className="row-label">{f.label}</div>
                <div className="row-sub">{f.type}{f.isCustom ? " · Custom" : ""}</div>
              </div>
              <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
                {f.isCustom && (
                  <button className="btn ghost sm" title="Remove field" onClick={() => removeField(f)}><IcTrash size={14} /></button>
                )}
                <button
                  className={`toggle ${f.visible ? "on" : ""}`}
                  disabled={busyKey === f.key}
                  onClick={() => toggleVisible(f)}
                  title={f.visible ? "Visible — click to hide" : "Hidden — click to show"}
                />
              </div>
            </div>
          ))}
        </div>
      ))}

      {showNew && (
        <NewFieldModal
          types={meta.types}
          categories={meta.categories}
          onCreate={createField}
          onClose={() => setShowNew(false)}
        />
      )}
    </>
  );
}

// Admin → Workflow labels: manage the per-org list of asset statuses.
// Create / rename / recolor / reorder / delete-with-migration.
function WorkflowLabelsSection() {
  const COLORS = ["green", "yellow", "gray", "red", "blue", "purple"];
  const { labels, reload } = window.VandayServer.useWorkflowLabels();
  const [draftName, setDraftName] = React.useState("");
  const [draftColor, setDraftColor] = React.useState("green");
  const [editingId, setEditingId] = React.useState(null);
  const [editName, setEditName] = React.useState("");
  const [editColor, setEditColor] = React.useState("green");
  const [deletingId, setDeletingId] = React.useState(null);
  const [migrateTo, setMigrateTo] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState(null);

  // Friendlier wording for the most common backend error codes. Anything
  // else falls through to the raw message.
  const friendlyError = (msg) => {
    if (!msg) return null;
    const s = String(msg);
    if (s.includes("duplicate") || s.includes("UNIQUE")) return "A label with that name already exists.";
    if (s.includes("invalid_color")) return "Pick one of the six allowed colors.";
    if (s.includes("empty_name") || s.includes("required")) return "Give the label a name.";
    if (s.includes("migrate_target_not_found")) return "The label to move assets to no longer exists.";
    return s;
  };

  const sorted = React.useMemo(() => {
    return [...(labels || [])].sort((a, b) => {
      const sa = a.sortOrder ?? a.sort_order ?? 0;
      const sb = b.sortOrder ?? b.sort_order ?? 0;
      if (sa !== sb) return sa - sb;
      return (a.name || "").localeCompare(b.name || "");
    });
  }, [labels]);

  const add = async () => {
    const name = draftName.trim();
    if (!name) return;
    setError(null); setBusy(true);
    try {
      await VandayAPI.createWorkflowLabel({ name, color: draftColor });
      setDraftName("");
      setDraftColor("green");
      await reload();
    } catch (e) { setError(e.message); }
    finally { setBusy(false); }
  };

  const beginEdit = (l) => {
    setEditingId(l.id);
    setEditName(l.name);
    setEditColor(l.color);
    setDeletingId(null);
    setError(null);
  };
  const saveEdit = async () => {
    if (!editingId) return;
    const name = editName.trim();
    if (!name) return;
    setError(null); setBusy(true);
    try {
      await VandayAPI.updateWorkflowLabel(editingId, { name, color: editColor });
      setEditingId(null);
      await reload();
    } catch (e) { setError(e.message); }
    finally { setBusy(false); }
  };

  const move = async (l, dir) => {
    const idx = sorted.findIndex((x) => x.id === l.id);
    const target = sorted[idx + dir];
    if (!target) return;
    setBusy(true); setError(null);
    try {
      const myOrder = l.sortOrder ?? l.sort_order ?? idx;
      const theirOrder = target.sortOrder ?? target.sort_order ?? (idx + dir);
      await Promise.all([
        VandayAPI.updateWorkflowLabel(l.id, { sortOrder: theirOrder }),
        VandayAPI.updateWorkflowLabel(target.id, { sortOrder: myOrder }),
      ]);
      await reload();
    } catch (e) { setError(e.message); }
    finally { setBusy(false); }
  };

  const beginDelete = (l) => {
    setDeletingId(l.id);
    setMigrateTo("");
    setEditingId(null);
    setError(null);
  };
  const confirmDelete = async () => {
    if (!deletingId) return;
    setBusy(true); setError(null);
    try {
      await VandayAPI.deleteWorkflowLabel(deletingId, migrateTo || null);
      setDeletingId(null);
      setMigrateTo("");
      await reload();
    } catch (e) { setError(e.message); }
    finally { setBusy(false); }
  };

  const ColorPalette = ({ value, onChange }) => (
    <div className="wf-swatch-row" role="radiogroup" aria-label="Color">
      {COLORS.map((c) => (
        <button
          key={c}
          type="button"
          role="radio"
          aria-checked={value === c}
          title={c}
          className={`wf-swatch wf-color-${c} ${value === c ? "is-active" : ""}`}
          onClick={() => onChange(c)}
        />
      ))}
    </div>
  );

  return (
    <>
      <h1 className="settings-h1">Workflow labels</h1>
      <p className="settings-sub" style={{ marginBottom: 20 }}>
        The statuses your team can apply to assets — e.g. <em>For review</em>, <em>Approved</em>, <em>Retired</em>.
        Anyone in the workspace can change an asset's status; only admins manage this list.
      </p>

      <div className="section-card">
        <h2 className="section-h">Current labels</h2>
        {sorted.length === 0 && (
          <p style={{ color: "var(--text-faint)", fontSize: 13, margin: 0 }}>
            No labels yet — add your first one below.
          </p>
        )}
        <div className="wf-label-list">
          {sorted.map((l, idx) => {
            if (editingId === l.id) {
              return (
                <div key={l.id} className="wf-label-row is-editing">
                  <ColorPalette value={editColor} onChange={setEditColor} />
                  <input
                    className="wf-label-input"
                    value={editName}
                    onChange={(e) => setEditName(e.target.value)}
                    onKeyDown={(e) => {
                      if (e.key === "Enter") saveEdit();
                      if (e.key === "Escape") setEditingId(null);
                    }}
                    autoFocus
                  />
                  <button className="btn sm accent" onClick={saveEdit} disabled={busy || !editName.trim()}>Save</button>
                  <button className="btn ghost sm" onClick={() => setEditingId(null)} disabled={busy}>Cancel</button>
                </div>
              );
            }
            if (deletingId === l.id) {
              const others = sorted.filter((x) => x.id !== l.id);
              return (
                <div key={l.id} className="wf-label-row is-deleting">
                  <span className={`wf-pill wf-color-${l.color}`}>
                    <span className="wf-dot" />{l.name}
                  </span>
                  <span style={{ fontSize: 12.5, color: "var(--text-muted)", flex: "1 1 auto" }}>
                    Assets currently tagged with this should:
                  </span>
                  <select
                    value={migrateTo}
                    onChange={(e) => setMigrateTo(e.target.value)}
                    className="wf-label-select"
                  >
                    <option value="">Become uncategorized</option>
                    {others.map((x) => (
                      <option key={x.id} value={x.id}>Move to "{x.name}"</option>
                    ))}
                  </select>
                  <button className="btn sm" onClick={confirmDelete} disabled={busy}>Confirm delete</button>
                  <button className="btn ghost sm" onClick={() => setDeletingId(null)} disabled={busy}>Cancel</button>
                </div>
              );
            }
            return (
              <div key={l.id} className="wf-label-row">
                <span className={`wf-pill wf-color-${l.color}`}>
                  <span className="wf-dot" />{l.name}
                </span>
                <div style={{ flex: 1 }} />
                <button
                  className="btn ghost sm"
                  onClick={() => move(l, -1)}
                  disabled={idx === 0 || busy}
                  title="Move up"
                >↑</button>
                <button
                  className="btn ghost sm"
                  onClick={() => move(l, +1)}
                  disabled={idx === sorted.length - 1 || busy}
                  title="Move down"
                >↓</button>
                <button className="btn ghost sm" onClick={() => beginEdit(l)} disabled={busy}>Edit</button>
                <button className="btn ghost sm" onClick={() => beginDelete(l)} disabled={busy}>Delete</button>
              </div>
            );
          })}
        </div>
      </div>

      <div className="section-card">
        <h2 className="section-h">Add a label</h2>
        {error && (
          <div style={{
            background: "oklch(0.94 0.06 25)", color: "oklch(0.36 0.16 25)",
            padding: "8px 12px", borderRadius: 6, fontSize: 12.5, marginBottom: 12,
          }}>{friendlyError(error)}</div>
        )}
        <div style={{ display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap" }}>
          <ColorPalette value={draftColor} onChange={setDraftColor} />
          <input
            className="wf-label-input"
            value={draftName}
            onChange={(e) => setDraftName(e.target.value)}
            onKeyDown={(e) => { if (e.key === "Enter") add(); }}
            placeholder="Label name (e.g. Needs revision)"
            style={{ flex: "1 1 200px" }}
          />
          <button className="btn accent" onClick={add} disabled={busy || !draftName.trim()}>
            <IcPlus size={12} /> Add
          </button>
        </div>
      </div>
    </>
  );
}

function BillingSection() {
  // Whether paid billing is actually rolled out for THIS workspace. Until it
  // is (normal beta), the tab shows the current plan + a "coming soon" teaser
  // instead of live pricing / checkout. Guard the hook result — it can be null
  // on the first render before features load.
  const planFeatures = (typeof window.useVandayFeatures === "function") ? window.useVandayFeatures() : null;
  const billingLive = planFeatures ? planFeatures.usageBilling === true : false;
  const [data, setData] = React.useState(null);
  const [err, setErr] = React.useState(null);
  // Per-plan busy state: { [planId]: 'checkout'|'trial' } | { portal: true }
  const [busy, setBusy] = React.useState({});
  // Billing cycle toggle for the plan grid. 'annual' = 20% off (already baked
  // into the per-plan pricing on the catalog). Persists in localStorage so a
  // user who selected annual once doesn't have to re-pick on every visit.
  const [cycle, setCycle] = React.useState(() => {
    try { return window.localStorage.getItem("vanday_billing_cycle") || "annual"; }
    catch { return "annual"; }
  });
  React.useEffect(() => {
    try { window.localStorage.setItem("vanday_billing_cycle", cycle); } catch {}
  }, [cycle]);

  const load = React.useCallback(() => {
    window.VandayAPI.getBilling()
      .then((d) => { setData(d); setErr(null); })
      .catch((e) => setErr(e.message || "Failed to load billing"));
  }, []);
  React.useEffect(() => { load(); }, [load]);

  // After returning from Stripe (?billing=success|cancel|return), show a note
  // and re-load — the webhook may have just changed the plan.
  const [flash] = React.useState(() => {
    try { return new URLSearchParams(window.location.search).get("billing"); }
    catch { return null; }
  });
  React.useEffect(() => {
    if (!flash) return;
    try {
      const u = new URL(window.location.href);
      u.searchParams.delete("billing");
      window.history.replaceState({}, "", u);
    } catch {}
    const t = setTimeout(load, 1500);
    return () => clearTimeout(t);
  }, [flash, load]);

  const upgrade = async (planId) => {
    setBusy((b) => ({ ...b, [planId]: "checkout" }));
    try {
      const { url } = await window.VandayAPI.billingCheckout(planId, cycle);
      window.location.href = url;
    } catch (e) {
      setBusy((b) => { const n = { ...b }; delete n[planId]; return n; });
      window.alert(e.message || "Couldn't start checkout");
    }
  };
  const startTrial = async (planId) => {
    setBusy((b) => ({ ...b, [planId]: "trial" }));
    try {
      await window.VandayAPI.startTrial(planId);
      load();
    } catch (e) {
      window.alert(e.message || "Couldn't start the trial");
    } finally {
      setBusy((b) => { const n = { ...b }; delete n[planId]; return n; });
    }
  };
  // Change plan on an EXISTING subscription. The server decides up vs down by
  // tier; we just confirm the right expectation and report the result.
  const changePlan = async (planId, goingUp, label) => {
    const ok = goingUp
      ? window.confirm(`Upgrade to ${label} now?\n\nYou'll be charged the prorated difference for the rest of this billing period immediately, and the new features unlock right away.`)
      : window.confirm(`Downgrade to ${label}?\n\nYou'll keep your current plan and its features until the end of this billing period — no charge now — then move to ${label}.`);
    if (!ok) return;
    setBusy((b) => ({ ...b, [planId]: "change" }));
    try {
      const r = await window.VandayAPI.changePlan(planId, cycle);
      if (r.applied === "upgraded") {
        window.alert(`You're now on ${label}. The prorated difference was charged to your card.`);
      } else {
        window.alert(`Done — you'll keep your current plan until ${fmtDateSec(r.effectiveAt) || "the end of this period"}, then switch to ${label}. No charge today.`);
      }
      load();
    } catch (e) {
      window.alert(e.message || "Couldn't change your plan.");
    } finally {
      setBusy((b) => { const n = { ...b }; delete n[planId]; return n; });
    }
  };
  const cancelDowngrade = async () => {
    setBusy((b) => ({ ...b, cancelDown: true }));
    try {
      await window.VandayAPI.cancelDowngrade();
      load();
    } catch (e) {
      window.alert(e.message || "Couldn't cancel the scheduled change.");
    } finally {
      setBusy((b) => { const n = { ...b }; delete n.cancelDown; return n; });
    }
  };
  const manage = async () => {
    setBusy((b) => ({ ...b, portal: true }));
    try {
      const { url } = await window.VandayAPI.billingPortal();
      window.location.href = url;
    } catch (e) {
      setBusy((b) => { const n = { ...b }; delete n.portal; return n; });
      window.alert(e.message || "Couldn't open the billing portal");
    }
  };

  if (err) {
    return (<>
      <h1 className="settings-h1">Plan &amp; billing</h1>
      <p className="settings-sub" style={{ color: "var(--status-danger, #b3261e)" }}>{err}</p>
    </>);
  }
  if (!data) {
    return (<>
      <h1 className="settings-h1">Plan &amp; billing</h1>
      <p className="settings-sub">Loading…</p>
    </>);
  }

  const onPaid = data.status === "active" || data.status === "past_due";
  const trial = data.trial; // { plan, endsAt, daysLeft } | null
  const fmtDateSec = (s) => s
    ? new Date(s * 1000).toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" })
    : null;
  const fmtDateMs = (ms) => ms
    ? new Date(ms).toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" })
    : null;
  const fmtBytes = (b) => {
    if (b === "unlimited") return "Unlimited";
    if (b >= 1024 * 1024 * 1024 * 1024) return `${Math.round(b / (1024 * 1024 * 1024 * 1024))} TB`;
    return `${Math.round(b / (1024 * 1024 * 1024))} GB`;
  };
  const fmtUploads = (n) =>
    n === "unlimited" ? "Unlimited uploads" : `${n.toLocaleString()} uploads`;
  const fmtPrice = (dollars) => {
    if (dollars == null) return null;
    return Number.isInteger(dollars)
      ? `$${dollars}`
      : `$${dollars.toFixed(2)}`;
  };

  // The current plan card's subtitle changes based on which state machine
  // is active: trial, paid sub, or grandfathered free_beta.
  const subLine = (() => {
    if (trial) {
      return `Free trial — ${trial.daysLeft} day${trial.daysLeft === 1 ? "" : "s"} left · ends ${fmtDateMs(trial.endsAt)}`;
    }
    if (onPaid) {
      const cyc = data.billingCycle === "annual" ? "Billed annually" : "Billed monthly";
      return `${cyc}${data.currentPeriodEnd ? ` · renews ${fmtDateSec(data.currentPeriodEnd)}` : ""}`;
    }
    return "Free while in beta";
  })();

  return (
    <>
      <h1 className="settings-h1">Plan &amp; billing</h1>
      <p className="settings-sub">
        {billingLive
          ? "Manage your plan, payment method and invoices."
          : "Your current plan and what's included."}
      </p>

      {flash === "success" && (
        <div className="section-card" style={{ borderColor: "var(--status-success, #2e7d32)" }}>
          ✓ You're subscribed — your plan updates within a few seconds.
        </div>
      )}
      {flash === "cancel" && (
        <div className="section-card">Checkout canceled — no charge was made.</div>
      )}
      {!data.configured && (
        <div className="section-card">Billing isn't configured on this environment yet.</div>
      )}

      {/* ---- Current plan ---- */}
      <div className="section-card" style={{ background: "linear-gradient(180deg, var(--accent-soft) 0%, var(--surface) 90%)" }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start" }}>
          <div>
            <span className="role-pill" style={{ background: "white", borderColor: "transparent" }}>
              {trial ? "Trial" : onPaid ? "Current plan" : "Current plan"}
            </span>
            <h2 style={{ fontSize: 28, fontWeight: 500, margin: "12px 0 4px", letterSpacing: "-0.02em" }}>
              {data.planLabel}
            </h2>
            <p style={{ fontSize: 13, color: "var(--text-muted)", margin: 0 }}>{subLine}</p>
            {trial && data.isAdmin && data.configured && (
              <p style={{ fontSize: 12.5, color: "var(--text-muted)", margin: "10px 0 0", lineHeight: 1.55 }}>
                Add a card before the trial ends to keep {data.planLabel} features. Otherwise your workspace returns to the {data.underlyingPlanLabel} plan.
              </p>
            )}
          </div>
          <div style={{ display: "flex", flexDirection: "column", gap: 8, alignItems: "flex-end" }}>
            {onPaid && data.isAdmin && (
              <button className="btn" disabled={busy.portal} onClick={manage} title="Update card, view invoices">
                {busy.portal ? "Opening…" : "Manage payment & invoices"}
              </button>
            )}
            {trial && data.isAdmin && data.configured && (
              <button
                className="btn primary"
                disabled={!!busy[data.plan]}
                onClick={() => upgrade(data.plan)}
              >
                {busy[data.plan] === "checkout" ? "Opening…" : "Subscribe Now"}
              </button>
            )}
          </div>
        </div>
      </div>

      {/* ---- Pending downgrade banner ---- */}
      {data.pendingChange && (
        <div className="section-card" style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, flexWrap: "wrap", borderColor: "var(--accent)" }}>
          <div style={{ fontSize: 13.5, lineHeight: 1.5 }}>
            <strong>Scheduled change:</strong> your plan switches to <strong>{data.pendingChange.planLabel}</strong>
            {data.pendingChange.effectiveAt ? <> on <strong>{fmtDateSec(data.pendingChange.effectiveAt)}</strong></> : " at the end of this billing period"}.
            You keep your current plan and its features until then — no charge now.
          </div>
          {data.isAdmin && (
            <button className="btn sm" disabled={busy.cancelDown} onClick={cancelDowngrade} style={{ whiteSpace: "nowrap" }}>
              {busy.cancelDown ? "Cancelling…" : "Keep current plan"}
            </button>
          )}
        </div>
      )}

      {/* ---- Beta teaser (billing not rolled out for this workspace yet) ---- */}
      {!billingLive && (
        <div className="section-card" style={{ display: "flex", gap: 12, alignItems: "flex-start" }}>
          <IcSparkles size={16} style={{ color: "var(--accent)", flexShrink: 0, marginTop: 1 }} />
          <div style={{ fontSize: 13, color: "var(--text-muted)", lineHeight: 1.55 }}>
            <strong style={{ color: "var(--text)" }}>Paid plans are coming soon.</strong> Vanday is free
            while you're in the beta. When paid plans open up, you'll be able to choose one right here —
            we'll give you plenty of notice first, and your files are always retained for 30 days.
          </div>
        </div>
      )}

      {/* ---- Plan grid (Starter / Growth / Pro) — only once billing is live ---- */}
      {billingLive && data.configured && data.plans.length > 0 && (
        <div className="section-card">
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 12, marginBottom: 16, flexWrap: "wrap" }}>
            <h2 className="section-h" style={{ margin: 0 }}>
              {onPaid ? "Change plan" : trial ? "Skip the trial — upgrade now" : "Plans"}
            </h2>
            {/* Monthly / annual toggle. Annual = ~20% off (the discount is
                already in the per-plan pricing from the server catalog). */}
            <div role="tablist" style={{ display: "inline-flex", padding: 2, background: "var(--surface-sunken)", borderRadius: 999, fontSize: 12.5 }}>
              {[{ id: "monthly", label: "Monthly" }, { id: "annual", label: "Annual · save 20%" }].map((c) => (
                <button
                  key={c.id}
                  role="tab"
                  aria-selected={cycle === c.id}
                  onClick={() => setCycle(c.id)}
                  style={{
                    padding: "6px 12px",
                    borderRadius: 999,
                    border: 0,
                    background: cycle === c.id ? "var(--surface)" : "transparent",
                    color: cycle === c.id ? "var(--text)" : "var(--text-muted)",
                    fontWeight: cycle === c.id ? 600 : 500,
                    cursor: "pointer",
                    boxShadow: cycle === c.id ? "0 1px 2px rgba(0,0,0,0.06)" : "none",
                  }}
                >
                  {c.label}
                </button>
              ))}
            </div>
          </div>
          {!data.isAdmin && (
            <p style={{ fontSize: 13, color: "var(--text-muted)", margin: "0 0 12px" }}>
              Only a workspace admin can change the plan.
            </p>
          )}
          <div style={{ display: "grid", gridTemplateColumns: `repeat(${Math.min(data.plans.length, 4)}, 1fr)`, gap: 14 }}>
            {data.plans.map((p) => {
              const isCurrent = data.plan === p.id;
              const isTrialingThis = trial?.plan === p.id;
              const price = p.pricing?.[cycle] ?? null;
              // Tier direction vs the current plan (for paid subscribers): a
              // higher monthly list price is an upgrade (charge now), lower is a
              // downgrade (next cycle). free_beta → 0.
              const currentMonthly = (data.plans.find((x) => x.id === data.plan)?.pricing?.monthly)
                ?? (data.freeBetaPlan?.pricing?.monthly) ?? 0;
              const isUp = (p.pricing?.monthly ?? 0) > currentMonthly;
              return (
                <div
                  key={p.id}
                  className="section-card"
                  style={{
                    margin: 0,
                    borderColor: isCurrent ? "var(--accent)" : "var(--border)",
                    borderWidth: isCurrent ? 1.5 : 1,
                  }}
                >
                  <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline" }}>
                    <div style={{ fontSize: 16, fontWeight: 600 }}>{p.label}</div>
                    {isCurrent && !trial && (
                      <span style={{ fontSize: 10.5, fontWeight: 600, color: "var(--accent)", letterSpacing: "0.06em", textTransform: "uppercase" }}>
                        Current
                      </span>
                    )}
                    {isTrialingThis && (
                      <span style={{ fontSize: 10.5, fontWeight: 600, color: "var(--accent)", letterSpacing: "0.06em", textTransform: "uppercase" }}>
                        Trialing
                      </span>
                    )}
                  </div>
                  <div style={{ margin: "10px 0 4px", lineHeight: 1 }}>
                    <span style={{ fontSize: 26, fontWeight: 500, letterSpacing: "-0.02em" }}>
                      {price != null ? fmtPrice(cycle === "annual" ? price / 12 : price) : "—"}
                    </span>
                    <span style={{ fontSize: 12.5, color: "var(--text-muted)", marginLeft: 4 }}>
                      / mo
                    </span>
                  </div>
                  {cycle === "annual" && price != null && (
                    <div style={{ fontSize: 11.5, color: "var(--text-faint)", marginBottom: 8 }}>
                      Billed {fmtPrice(price)} yearly
                    </div>
                  )}
                  <ul style={{ listStyle: "none", padding: 0, margin: "10px 0 16px", fontSize: 12.5, color: "var(--text-muted)", lineHeight: 1.7 }}>
                    <li>{fmtBytes(p.limits.maxStorageBytes)} storage</li>
                    <li>{p.limits.maxSeats} team {p.limits.maxSeats === 1 ? "member" : "members"}</li>
                    <li>{fmtUploads(p.limits.maxUploads)}</li>
                    {p.features?.aiAutoTagging && <li>AI auto-tagging</li>}
                    {p.features?.socialPublishing
                      ? <li>Social publishing</li>
                      : <li style={{ color: "var(--text-faint)" }}>Social publishing — not included</li>
                    }
                  </ul>
                  {/* CTA logic depends on what state the user is in.
                      onPaid (active Stripe sub):
                        - current plan -> 'Current plan' marker, no CTA
                        - other plan   -> 'Switch to X' button that opens the
                                          Stripe Portal (Stripe handles the
                                          plan swap + proration; hitting
                                          /api/billing/checkout again would
                                          create a SECOND sub instead of
                                          changing the existing one).
                      trial (in 14-day in-app trial):
                        - trial plan   -> 'Convert to X' button -> Checkout
                        - other plan   -> 'Upgrade to X' button -> Checkout
                                          (no trial button — Stripe locks in
                                          payment immediately)
                      free_beta (default):
                        - any plan     -> 'Upgrade' + 'Start free trial' */}
                  {isCurrent && !trial ? (
                    <div
                      style={{
                        textAlign: "center",
                        fontSize: 12,
                        fontWeight: 600,
                        color: "var(--accent)",
                        letterSpacing: "0.06em",
                        textTransform: "uppercase",
                        padding: "10px 0 2px",
                      }}
                    >
                      Your current plan
                    </div>
                  ) : onPaid ? (
                    <button
                      className={isUp ? "btn primary" : "btn"}
                      style={{ width: "100%" }}
                      disabled={!data.isAdmin || !!busy[p.id]}
                      onClick={() => changePlan(p.id, isUp, p.label)}
                      title={isUp
                        ? "Upgrade now — prorated charge applied immediately"
                        : "Downgrade — takes effect at the end of your billing period"}
                    >
                      {busy[p.id] === "change"
                        ? "Working…"
                        : isUp ? `Upgrade to ${p.label}` : `Downgrade to ${p.label}`}
                    </button>
                  ) : (
                    <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
                      <button
                        className="btn primary"
                        style={{ width: "100%" }}
                        disabled={!data.isAdmin || !!busy[p.id]}
                        onClick={() => upgrade(p.id)}
                        title={cycle === "annual" ? `Pay ${fmtPrice(price)} for a year` : `Pay ${fmtPrice(price)}/month`}
                      >
                        {busy[p.id] === "checkout" ? "Opening…" : isTrialingThis ? "Subscribe Now" : isUp ? "Upgrade" : "Downgrade"}
                      </button>
                      {!trial && (
                        <button
                          className="btn"
                          style={{ width: "100%" }}
                          disabled={!data.isAdmin || !!busy[p.id]}
                          onClick={() => startTrial(p.id)}
                        >
                          {busy[p.id] === "trial" ? "Starting…" : "Start 14-day free trial"}
                        </button>
                      )}
                    </div>
                  )}
                </div>
              );
            })}
          </div>
          <p style={{ fontSize: 11.5, color: "var(--text-faint)", marginTop: 12 }}>
            Final pricing is shown at checkout. Free trials don't require a card. Invoices and payment method are managed in the billing portal.
          </p>
        </div>
      )}
    </>
  );
}

function UsageSection() {
  const limits = (typeof window.useVandayLimits === "function") ? window.useVandayLimits() : null;
  const storageCapBytes = limits?.maxStorageBytes ?? (5 * 1024 * 1024 * 1024);
  const storageUsedBytes = limits?.storageBytesUsed ?? 0;
  const capGB = Math.round(storageCapBytes / (1024 * 1024 * 1024));
  const seatCap = limits?.maxSeats ?? 3;
  const fmtSize = (bytes) => {
    const gb = (bytes || 0) / (1024 * 1024 * 1024);
    if (gb < 0.1) return ((bytes || 0) / (1024 * 1024)).toFixed(1) + " MB";
    return gb.toFixed(2) + " GB";
  };

  // Real seat usage against the cap. Viewers (view + download only) are
  // unlimited and never count — only billable (non-viewer) members + invites.
  const [seats, setSeats] = React.useState(null);
  React.useEffect(() => {
    let alive = true;
    window.VandayAPI.getOrgAccess().then((a) => {
      if (!alive) return;
      const mem = Array.isArray(a?.members) ? a.members : [];
      const inv = Array.isArray(a?.invites) ? a.invites : [];
      const members = mem.length ? mem.filter((m) => m.role !== "viewer").length : 1;
      const pending = inv.filter((iv) => !iv.acceptedAt && iv.role !== "viewer").length;
      const viewers = mem.filter((m) => m.role === "viewer").length;
      setSeats({ members, pending, viewers });
    }).catch(() => {});
    return () => { alive = false; };
  }, []);

  const nav = (id) => window.dispatchEvent(new CustomEvent("vanday-nav-settings", { detail: id }));
  const usedSeats = seats ? seats.members + seats.pending : null;
  const blocks = [
    {
      name: "Members",
      value: usedSeats != null ? `${usedSeats} of ${seatCap}` : "—",
      sub: seats
        ? `Editors & admins use a seat${seats.viewers ? ` · ${seats.viewers} viewer${seats.viewers === 1 ? "" : "s"} free` : ""}`
        : "Loading…",
      progress: usedSeats != null && seatCap ? usedSeats / seatCap : 0,
      action: "Manage members", onAction: () => nav("users"),
    },
    {
      name: "Storage",
      value: `${fmtSize(storageUsedBytes)} of ${capGB} GB`,
      sub: "Includes versions and trash",
      progress: storageCapBytes ? storageUsedBytes / storageCapBytes : 0,
      action: null,
    },
  ];

  return (
    <>
      <h1 className="settings-h1">Usage</h1>
      <p className="settings-sub">Monitor your account usage and plan limits.</p>
      {blocks.map((b) => (
        <div className="section-card" key={b.name}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start" }}>
            <div>
              <h2 className="section-h" style={{ marginBottom: 4 }}>{b.name}</h2>
              <div style={{ fontSize: 13, color: "var(--text-muted)" }}>
                <span style={{ color: "var(--text)", fontWeight: 500 }}>{b.value}</span>
                {" · "}{b.sub}
              </div>
            </div>
            {b.action && <button className="btn sm" onClick={b.onAction}>{b.action}</button>}
          </div>
          <div style={{ marginTop: 14, height: 6, background: "var(--surface-sunken)", borderRadius: 999, overflow: "hidden" }}>
            <div style={{
              width: `${Math.max(2, Math.min(100, b.progress * 100))}%`, height: "100%",
              background: b.progress > 0.8 ? "oklch(0.62 0.18 28)" : "var(--accent)",
            }} />
          </div>
        </div>
      ))}
    </>
  );
}

// Full-size usage meter (DAM Refresh design). Bar shifts accent -> amber
// (>=75%) -> red (>=90%); foot copy changes tone with it. Pass an explicit
// `pct` when used/total aren't comparable numbers (e.g. "61.2 GB" / "3 TB").
function UsageMeter({ label, icon, used, total, unit, left, reset, pct, tone }) {
  const ratio = pct != null ? pct : Math.min(100, Math.round((used / total) * 100));
  const state = tone || (ratio >= 90 ? "danger" : ratio >= 75 ? "warning" : "ok");
  const color = state === "danger" ? "var(--status-danger)"
    : state === "warning" ? "var(--status-warning)"
    : "var(--accent)";
  return (
    <div style={{ border: "1px solid var(--border)", background: "var(--surface)", borderRadius: "var(--radius)", padding: "18px 20px" }}>
      <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", marginBottom: 12 }}>
        <span style={{ fontSize: 13.5, fontWeight: 600, display: "inline-flex", alignItems: "center", gap: 8 }}>{icon} {label}</span>
        <span style={{ fontFamily: "var(--font-mono)", fontSize: 14 }}>
          <b style={{ color: "var(--text)", fontWeight: 600 }}>{used}</b>{" "}
          <span style={{ color: "var(--text-faint)" }}>/ {total}{unit ? ` ${unit}` : ""}</span>
        </span>
      </div>
      <div style={{ height: 9, background: "var(--surface-sunken)", borderRadius: 999, overflow: "hidden" }}>
        <div style={{ width: ratio + "%", height: "100%", borderRadius: 999, background: color, transition: "width 500ms cubic-bezier(0.3,0.7,0.3,1)" }} />
      </div>
      <div style={{ marginTop: 11, fontSize: 12.5, display: "flex", alignItems: "center", justifyContent: "space-between" }}>
        {state === "ok"
          ? <span style={{ color: "var(--text-muted)", display: "inline-flex", alignItems: "center", gap: 7 }}><IcCheck size={13} /> {left} left this cycle</span>
          : <span style={{ color: color, fontWeight: 500, display: "inline-flex", alignItems: "center", gap: 6 }}><IcInfo size={13} /> {left} left — {state === "danger" ? "at your limit" : "approaching your limit"}</span>}
        <span style={{ color: "var(--text-faint)", fontFamily: "var(--font-mono)", fontSize: 11.5 }}>{reset}</span>
      </div>
    </div>
  );
}

// DEPRECATED / UNUSED as of the Plan+Billing merge — the single "Plan & billing"
// tab now renders BillingSection for both states (teaser + live). Kept only to
// avoid a large deletion in this no-build file; safe to remove later.
function PlanSection() {
  const gridStyle = { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 };

  // usageBilling gates whether real pricing/checkout is shown. Until it's on,
  // this tab shows the "paid plans coming soon" teaser instead of live plans.
  const planFeatures = (typeof window.useVandayFeatures === "function") ? window.useVandayFeatures() : null;
  const billingLive = planFeatures ? planFeatures.usageBilling === true : false;
  // Real upload + seat limits from the server (single source of truth) so the
  // numbers here can never drift from what's actually enforced.
  const limits = (typeof window.useVandayLimits === "function") ? window.useVandayLimits() : null;
  const seatCap = limits?.maxSeats ?? 3;
  // Storage is the beta cap now (uploads are unlimited by count) — derive the
  // numbers for the meter + the summary line.
  const storageCapBytes = limits?.maxStorageBytes ?? (5 * 1024 * 1024 * 1024);
  const storageUsedBytes = limits?.storageBytesUsed ?? 0;
  const storageCapGB = Math.round(storageCapBytes / (1024 * 1024 * 1024));
  const storageUsedGB = (storageUsedBytes / (1024 * 1024 * 1024)).toFixed(1);
  const storagePct = storageCapBytes ? Math.min(100, Math.round((storageUsedBytes / storageCapBytes) * 100)) : 0;
  const storageLeftGB = Math.max(0, storageCapGB - parseFloat(storageUsedGB)).toFixed(1);

  // Seat usage = current members + outstanding invites. Fetched lazily.
  const [seatsUsed, setSeatsUsed] = React.useState(null);
  React.useEffect(() => {
    let alive = true;
    (async () => {
      try {
        const a = await window.VandayAPI.getOrgAccess();
        if (!alive) return;
        // Viewers (view + download only) are unlimited — only billable
        // (non-viewer) members + invites count toward the seat cap.
        const mem = Array.isArray(a?.members) ? a.members : [];
        const inv = Array.isArray(a?.invites) ? a.invites : [];
        const members = mem.length ? mem.filter((m) => m.role !== "viewer").length : 1;
        const pending = inv.filter((iv) => !iv.acceptedAt && iv.role !== "viewer").length;
        setSeatsUsed(members + pending);
      } catch { if (alive) setSeatsUsed(1); }
    })();
    return () => { alive = false; };
  }, []);

  const seats = seatsUsed == null ? 1 : seatsUsed;

  // ---- Billing (Stripe) — live plan + upgrade/manage ----
  const [billing, setBilling] = React.useState(null);
  const loadBilling = React.useCallback(() => {
    window.VandayAPI.getBilling()
      .then(setBilling)
      .catch(() => setBilling({ configured: false, plan: "free_beta", planLabel: "Free Beta", plans: [] }));
  }, []);
  React.useEffect(() => { loadBilling(); }, [loadBilling]);

  const [busy, setBusy] = React.useState(null); // plan id mid-checkout, or "portal"
  const [flash, setFlash] = React.useState(() => {
    try { return new URLSearchParams(window.location.search).get("billing"); } catch { return null; }
  });
  React.useEffect(() => {
    if (!flash) return;
    try {
      const u = new URL(window.location.href);
      u.searchParams.delete("billing");
      window.history.replaceState({}, "", u);
    } catch {}
    const t = setTimeout(loadBilling, 1500);
    return () => clearTimeout(t);
  }, [flash, loadBilling]);

  const [cycle, setCycle] = React.useState("monthly"); // "monthly" | "annual"
  const upgrade = async (planId) => {
    setBusy(planId);
    try { const { url } = await window.VandayAPI.billingCheckout(planId, cycle); window.location.href = url; }
    catch (e) { setBusy(null); window.alert(e.message || "Couldn't start checkout"); }
  };
  const manage = async () => {
    setBusy("portal");
    try { const { url } = await window.VandayAPI.billingPortal(); window.location.href = url; }
    catch (e) { setBusy(null); window.alert(e.message || "Couldn't open the billing portal"); }
  };

  const onPaid = !!(billing && billing.plan && billing.plan !== "free_beta");
  const isAdmin = !!(billing && billing.isAdmin);
  const planLabel = (billing && billing.planLabel) || "Free Beta";
  const fmtDate = (s) => s
    ? new Date(s * 1000).toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" })
    : null;
  const fmtGB = (b) => `${Math.round(b / (1024 * 1024 * 1024))} GB`;
  const fmtStorage = (b) => {
    if (typeof b !== "number") return String(b);
    const gb = b / (1024 * 1024 * 1024);
    return gb >= 1024 ? `${Math.round(gb / 1024)} TB` : `${Math.round(gb)} GB`;
  };
  const planSub = onPaid
    ? `${billing.status === "trialing" ? "Free trial" : "Billed monthly"}${billing.currentPeriodEnd ? ` · renews ${fmtDate(billing.currentPeriodEnd)}` : ""}`
    : "You're on the free beta plan";
  // Real pricing only when billing is actually rolled out for this workspace
  // (usageBilling). Otherwise the tab falls through to "paid plans coming soon".
  const showUpgrade = !!(billingLive && billing && !onPaid && billing.configured && isAdmin && billing.plans && billing.plans.length);

  return (
    <>
      <h1 className="settings-h1">Plan &amp; usage</h1>
      <p className="settings-sub">Track what you've used this cycle. {planLabel} includes {storageCapGB}GB of storage and up to {seatCap} team members.</p>

      {flash === "success" && (
        <div style={{ padding: "12px 16px", borderRadius: "var(--radius)", background: "var(--accent-soft)", border: "1px solid var(--accent)", color: "var(--text)", fontSize: 13, marginBottom: 14 }}>
          ✓ You're subscribed — your plan updates within a few seconds.
        </div>
      )}
      {flash === "cancel" && (
        <div style={{ padding: "12px 16px", borderRadius: "var(--radius)", background: "var(--surface-sunken)", border: "1px solid var(--border)", color: "var(--text-muted)", fontSize: 13, marginBottom: 14 }}>
          Checkout canceled — no charge was made.
        </div>
      )}

      <div style={{ display: "flex", alignItems: "center", gap: 16, padding: "18px 20px", border: "1px solid var(--border)", borderRadius: "var(--radius)", background: "var(--surface)", marginBottom: 18 }}>
        <div style={{ width: 46, height: 46, borderRadius: 12, background: "var(--accent-soft)", color: "var(--accent)", display: "grid", placeItems: "center", flexShrink: 0 }}><IcSparkles size={22} /></div>
        <div>
          <div style={{ fontSize: 15, fontWeight: 600 }}>{planLabel}</div>
          <div style={{ fontSize: 12.5, color: "var(--text-muted)", marginTop: 2 }}>{planSub}</div>
        </div>
        <div style={{ marginLeft: "auto" }}>
          {onPaid && isAdmin ? (
            <button className="btn primary" disabled={busy === "portal"} onClick={manage}>
              {busy === "portal" ? "Opening…" : "Manage plan"}
            </button>
          ) : !showUpgrade ? (
            <span style={{ display: "inline-flex", alignItems: "center", gap: 6, padding: "7px 14px", borderRadius: 999, background: "var(--surface-sunken)", border: "1px solid var(--border)", color: "var(--text-muted)", fontSize: 12.5, fontWeight: 600, whiteSpace: "nowrap" }}>
              <IcSparkles size={13} /> Paid plans coming soon
            </span>
          ) : null}
        </div>
      </div>

      {!onPaid && (
        <div style={{ display: "flex", gap: 10, alignItems: "flex-start", marginTop: 14, padding: "12px 14px", borderRadius: 10, background: "var(--surface-sunken)", border: "1px solid var(--border)" }}>
          <IcSparkles size={15} style={{ color: "var(--accent, #c8553d)", flexShrink: 0, marginTop: 1 }} />
          <div style={{ fontSize: 12.5, color: "var(--text-muted)", lineHeight: 1.5 }}>
            Vanday is <strong style={{ color: "var(--text)" }}>free during the beta</strong>. When the beta wraps up, you'll choose a paid plan to keep your workspace — we'll give you plenty of notice first, and your files are always retained for 30 days.
          </div>
        </div>
      )}

      <div style={gridStyle}>
        <UsageMeter label="Storage" icon={<IcImage size={15} />} used={storageUsedGB} total={storageCapGB} unit="GB" pct={storagePct} left={`${storageLeftGB} GB`} reset={onPaid ? "plan limit" : "beta limit"} />
        <UsageMeter label="Team seats" icon={<IcUsers size={15} />} used={seats} total={seatCap} left={Math.max(0, seatCap - seats)} reset={onPaid ? "plan limit" : "beta limit"} />
      </div>

      {showUpgrade && (
        <div style={{ marginTop: 26 }}>
          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", flexWrap: "wrap", gap: 12, marginBottom: 16 }}>
            <h2 className="section-h" style={{ margin: 0 }}>Upgrade your plan</h2>
            {/* Monthly / annual billing-cycle toggle */}
            <div style={{ display: "inline-flex", padding: 3, borderRadius: 999, background: "var(--surface-sunken)", border: "1px solid var(--border)" }}>
              {[["monthly", "Monthly"], ["annual", "Annual · save 20%"]].map(([id, label]) => (
                <button
                  key={id}
                  type="button"
                  onClick={() => setCycle(id)}
                  style={{
                    border: 0, cursor: "pointer", padding: "6px 14px", borderRadius: 999,
                    fontSize: 12.5, fontWeight: 600, whiteSpace: "nowrap",
                    background: cycle === id ? "var(--surface)" : "transparent",
                    color: cycle === id ? "var(--text)" : "var(--text-muted)",
                    boxShadow: cycle === id ? "0 1px 2px rgba(0,0,0,0.08)" : "none",
                    transition: "background 120ms",
                  }}
                >
                  {label}
                </button>
              ))}
            </div>
          </div>
          <div style={{ display: "grid", gridTemplateColumns: `repeat(${Math.min(billing.plans.length, 3)}, 1fr)`, gap: 14 }}>
            {billing.plans.map((p) => {
              const annual = cycle === "annual";
              const perMonth = annual && p.pricing ? p.pricing.annual / 12 : (p.pricing ? p.pricing.monthly : 0);
              const recommended = p.id === "growth";
              return (
                <div key={p.id} className="section-card" style={{ margin: 0, position: "relative", border: recommended ? "1.5px solid var(--accent)" : "1px solid var(--border)" }}>
                  {recommended && (
                    <span style={{ position: "absolute", top: -10, left: 16, fontSize: 10, fontWeight: 700, letterSpacing: 0.4, textTransform: "uppercase", color: "white", background: "var(--accent)", padding: "2px 8px", borderRadius: 999 }}>
                      Most popular
                    </span>
                  )}
                  <div style={{ fontSize: 15, fontWeight: 600 }}>{p.label}</div>
                  <div style={{ marginTop: 8, display: "flex", alignItems: "baseline", gap: 4 }}>
                    <span style={{ fontSize: 30, fontWeight: 700, letterSpacing: -0.5 }}>${Math.round(perMonth)}</span>
                    <span style={{ fontSize: 13, color: "var(--text-muted)" }}>/mo</span>
                  </div>
                  <div style={{ fontSize: 11.5, color: "var(--text-faint)", minHeight: 16, marginTop: 2 }}>
                    {annual && p.pricing ? `billed $${Math.round(p.pricing.annual).toLocaleString()}/year` : "billed monthly"}
                  </div>
                  <ul style={{ listStyle: "none", padding: 0, margin: "14px 0 18px", fontSize: 12.5, color: "var(--text-muted)", lineHeight: 1.9 }}>
                    <li>✓ {p.limits.maxUploads === "unlimited" ? "Unlimited uploads" : `${p.limits.maxUploads.toLocaleString()} uploads / mo`}</li>
                    <li>✓ {p.limits.maxSeats} team {p.limits.maxSeats === 1 ? "seat" : "seats"}</li>
                    <li>✓ {fmtStorage(p.limits.maxStorageBytes)} storage</li>
                    {p.features?.aiAutoTagging && <li>✓ AI auto-tagging</li>}
                    <li style={{ color: p.features?.socialPublishing ? "var(--text-muted)" : "var(--text-faint)" }}>
                      {p.features?.socialPublishing ? "✓ Social publishing" : "— Social publishing"}
                    </li>
                  </ul>
                  <button className={"btn" + (recommended ? " primary" : "")} style={{ width: "100%" }} disabled={busy === p.id} onClick={() => upgrade(p.id)}>
                    {busy === p.id ? "Starting…" : `Choose ${p.label}`}
                  </button>
                </div>
              );
            })}
          </div>
          <p style={{ fontSize: 11.5, color: "var(--text-faint)", marginTop: 12 }}>
            Secure checkout via Stripe. Change or cancel your plan anytime from the billing portal.
          </p>
        </div>
      )}
    </>
  );
}

function PlaceholderSection({ name }) {
  return (
    <>
      <h1 className="settings-h1">{name}</h1>
      <p className="settings-sub">This section is part of Vanday's full settings — content placeholder for the prototype.</p>
      <div className="section-card" style={{ padding: 48, textAlign: "center", color: "var(--text-muted)", fontSize: 13 }}>
        <div style={{
          width: 56, height: 56, borderRadius: 14, margin: "0 auto 16px",
          background: "var(--surface-sunken)", display: "grid", placeItems: "center",
          color: "var(--text-faint)",
        }}><IcSettings size={24} /></div>
        {name} settings would live here.
      </div>
    </>
  );
}

function SettingsPage({ onNav, onQuickUpload }) {
  // Honour a one-shot section hint (e.g. from the welcome modal's "Connect
  // accounts" button) so deep-linking into a specific section works.
  const initialSection = (() => {
    try {
      const hint = localStorage.getItem("vanday_settings_section");
      if (hint) {
        localStorage.removeItem("vanday_settings_section");
        return hint;
      }
    } catch {}
    return "users";
  })();
  const [section, setSection] = React.useState(initialSection);

  // Allow child components (like the overage banner in UsersSection) to
  // navigate to a different settings section without a page reload.
  React.useEffect(() => {
    const handler = (e) => setSection(e.detail);
    window.addEventListener("vanday-nav-settings", handler);
    return () => window.removeEventListener("vanday-nav-settings", handler);
  }, []);

  // Workspace, Plan, and the whole Admin group are admin-only. A non-admin who
  // somehow lands on one (e.g. the default "users" section, or a stale deep
  // link) is bounced to their Profile.
  const ADMIN_ONLY_SECTIONS = React.useMemo(() => new Set([
    "workspace", "plan", "usage", "billing", "branding",
    "users", "projects", "fields", "workflow",
    "integrations", "connections", "api", "audit", "security", "flags",
  ]), []);
  const features = (typeof window.useVandayFeatures === "function")
    ? window.useVandayFeatures()
    : null;
  const settingsLimits = (typeof window.useVandayLimits === "function") ? window.useVandayLimits() : null;
  const settingsPlanLabel = { free_beta: "Free Beta", starter: "Starter", growth: "Growth", pro: "Pro" }[settingsLimits?.planName] || "Free Beta";
  const featuresLoaded = !!(features && features.loaded);
  const isWsAdmin = features ? features.isWorkspaceAdmin === true : true;
  React.useEffect(() => {
    if (featuresLoaded && !isWsAdmin && ADMIN_ONLY_SECTIONS.has(section)) {
      setSection("profile");
    }
  }, [featuresLoaded, isWsAdmin, section, ADMIN_ONLY_SECTIONS]);
  const restricted = featuresLoaded && !isWsAdmin && ADMIN_ONLY_SECTIONS.has(section);

  const sectionTitle = {
    profile: "Profile", notifs: "Notifications",
    usage: "Usage", plan: "Plan & billing", billing: "Plan & billing", branding: "Branding",
    users: "Users", projects: "Projects", fields: "Fields",
    workflow: "Workflow labels",
    integrations: "Integrations", api: "API & webhooks",
    audit: "Audit log", security: "Content security",
  }[section];

  // Audit + Users + Integrations + API tables benefit from a wider content area.
  const wideSection = section === "audit" || section === "users" || section === "integrations" || section === "api" || section === "flags";

  return (
    <div className="app settings-shell">
      <Rail active="settings" onNav={onNav} onLogout={() => (window.__vandayLogout ? window.__vandayLogout() : onNav("login"))} onSearch={() => onNav("library")} onNotifs={() => onNav("library")} onHelp={() => onNav("library")} onQuickUpload={onQuickUpload} />
      <SettingsSidebar section={section} setSection={setSection} />
      <main className="main">
        <header className="topbar">
          <div className="crumbs">
            <span>Vanday studio</span>
            <span className="sep">/</span>
            <span>Settings</span>
            <span className="sep">/</span>
            <span className="here">{sectionTitle}</span>
          </div>
          <div className="topbar-right">
            <span className="trial-pill" title={settingsPlanLabel + " plan"}><span className="dot" /> {settingsPlanLabel}</span>
          </div>
        </header>
        <div className="settings-content" style={wideSection ? { maxWidth: 1200 } : undefined}>
          {restricted && (
            <div className="section-card" style={{ display: "flex", gap: 10, alignItems: "flex-start" }}>
              <IcShield size={16} />
              <div style={{ fontSize: 13, color: "var(--text-muted)" }}>
                This area is only available to workspace admins. Ask an admin if you need access.
              </div>
            </div>
          )}
          {!restricted && (
            <>
              {section === "profile"      && <ProfileSection />}
              {/* "connections" route legacy-redirects to integrations — they're merged. */}
              {section === "connections"  && <IntegrationsSection />}
              {section === "workspace"    && <WorkspaceSection />}
              {section === "users"    && <UsersSection />}
              {section === "fields"   && <FieldsSection />}
              {section === "workflow" && <WorkflowLabelsSection />}
              {/* Plan + billing merged into one tab. "billing" kept as an alias
                  so old deep links / the Stripe ?billing= return still resolve. */}
              {(section === "plan" || section === "billing") && <BillingSection />}
              {section === "usage"    && <UsageSection />}
              {section === "audit"    && <AuditLogSection />}
              {section === "integrations" && <IntegrationsSection />}
              {section === "api"      && <ApiSection />}
              {section === "flags"    && <FeatureFlagsSection />}
              {(section === "notifs" || section === "branding" ||
                section === "projects" || section === "security") &&
                <PlaceholderSection name={sectionTitle} />}
            </>
          )}
        </div>
      </main>
    </div>
  );
}

window.SettingsPage = SettingsPage;
