// Vanday DAM — Wave 2: trust & governance
//   • PermissionsModal — FT-17 per-folder permissions
//   • ShareModal       — GP-02 create a branded share portal
//   • SharePortalPage  — GP-02 the external-facing portal view
//   • AuditLogSection  — GP-11 activity audit log
//   • ExpiryField      — FT-15 expiration block for asset preview
//
// All of these are designed to play with the existing Vanday palette
// (warm off-white, orange accent, mono captions) and use the same
// component vocabulary as the rest of the app.

// Video assets can't be shown via a CSS background-image or <img>, so portal
// thumbnails (create-modal preview + the portal view) render a play badge for
// them instead of a blank tile. Detect by mime or filename extension.
function isVideoAsset(a) {
  return !!a && ((a.mime || "").startsWith("video/")
    || /\.(mov|mp4|m4v|webm|avi|mkv)$/i.test(a.name || ""));
}
function PortalPlayBadge({ size = 34 }) {
  return (
    <span
      aria-hidden="true"
      style={{
        position: "absolute", inset: 0, display: "grid", placeItems: "center",
        background: "linear-gradient(135deg, var(--surface-sunken, #edeae5), #ddd9d3)",
      }}
    >
      <span style={{
        width: size, height: size, borderRadius: "50%", display: "grid", placeItems: "center",
        background: "rgba(0,0,0,0.42)", border: "1px solid rgba(255,255,255,0.7)",
      }}>
        <svg viewBox="0 0 24 24" width={Math.round(size * 0.42)} height={Math.round(size * 0.42)}
          style={{ fill: "#fff", marginLeft: 2 }}><path d="M8 5v14l11-7z" /></svg>
      </span>
    </span>
  );
}

const ROLE_DESCRIPTIONS = {
  Owner:       "Full control · can change permissions and billing",
  Admin:       "Manage folder + invite people",
  Editor:      "Upload, edit, delete, publish",
  Contributor: "Upload + edit own assets",
  Commenter:   "View + leave comments",
  Viewer:      "Read-only access",
};

function RoleSelect({ value, onChange, options = ["Owner", "Admin", "Editor", "Contributor", "Commenter", "Viewer"], disabled }) {
  return (
    <select
      className="role-select"
      value={value}
      disabled={disabled}
      onChange={(e) => onChange && onChange(e.target.value)}
      onClick={(e) => e.stopPropagation()}
    >
      {options.map((r) => {
        const val = typeof r === "string" ? r : r.value;
        const label = typeof r === "string" ? r : (r.label || r.value);
        return <option key={val} value={val}>{label}</option>;
      })}
    </select>
  );
}

// ============================================================
// PermissionsModal — FT-17
// ============================================================
function PermissionsModal({ folder, onClose }) {
  const initial = FOLDER_PERMS[folder.id] || { members: [], link: "workspace" };
  const [members, setMembers] = React.useState(
    initial.members.map(([uid, role]) => ({ userId: uid, role }))
  );
  const [link, setLink] = React.useState(initial.link);
  const [inviteEmail, setInviteEmail] = React.useState("");
  const [inviteRole, setInviteRole] = React.useState("Editor");

  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 userById = (id) => USERS.find((u) => u.id === id);
  const changeRole = (uid, role) => setMembers((ms) => ms.map((m) => m.userId === uid ? { ...m, role } : m));
  const removeMember = (uid) => setMembers((ms) => ms.filter((m) => m.userId !== uid));
  const sendInvite = () => {
    if (!inviteEmail) return;
    const newId = `pending-${Date.now()}`;
    setMembers((ms) => [...ms, { userId: newId, role: inviteRole, pending: true, email: inviteEmail }]);
    setInviteEmail("");
  };

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal perm-modal" onClick={(e) => e.stopPropagation()}>
        <div className="modal-head">
          <div>
            <div style={{ display: "inline-flex", alignItems: "center", gap: 8, fontSize: 11, textTransform: "uppercase", letterSpacing: "0.06em", color: "var(--text-faint)", fontWeight: 500, marginBottom: 4 }}>
              <IcShield size={11} /> Permissions
            </div>
            <h2 className="modal-title">{folder.name}</h2>
            <div style={{ fontSize: 12.5, color: "var(--text-muted)", marginTop: 4 }}>
              Restrict who can see, edit, and publish assets in this folder. Changes apply immediately.
            </div>
          </div>
          <button className="btn ghost sm" onClick={onClose}><IcClose size={14} /></button>
        </div>

        <div className="perm-body">
          {/* Invite row — hidden during free beta. Team workspaces are a paid feature
              we haven't shipped yet; the row will return when plans launch. */}
          <div className="perm-section" style={{ opacity: 0.55 }}>
            <div className="perm-h">Invite people · coming soon</div>
            <div style={{ fontSize: 12.5, color: "var(--text-muted)", marginTop: 4 }}>
              Team workspaces aren't available on the free beta plan yet. We'll
              email you the moment invites unlock.
            </div>
          </div>

          {/* Members list */}
          <div className="perm-section">
            <div className="perm-h-row">
              <div className="perm-h">Members</div>
              <span className="perm-count">{members.length} people</span>
            </div>
            <div className="perm-list">
              {members.map((m) => {
                const u = userById(m.userId);
                const isOwner = m.role === "Owner";
                return (
                  <div className="perm-row" key={m.userId}>
                    <span className="pill-user" style={{ background: "transparent", padding: 0 }}>
                      <span className="av" style={{
                        background: u?.color || "var(--surface-sunken)",
                        width: 28, height: 28, fontSize: 11,
                      }}>
                        {u ? u.name.split(" ").map((s) => s[0]).join("") : (m.email || "?")[0].toUpperCase()}
                      </span>
                    </span>
                    <div className="perm-row-main">
                      <div className="perm-row-name">
                        {u ? u.name : (m.email || "Pending invite")}
                        {m.pending && <span className="perm-pending">Pending</span>}
                      </div>
                      <div className="perm-row-email">{u ? u.email : m.email}</div>
                    </div>
                    <div className="perm-row-role" title={ROLE_DESCRIPTIONS[m.role]}>
                      <RoleSelect
                        value={m.role}
                        onChange={(r) => changeRole(m.userId, r)}
                        disabled={isOwner}
                      />
                    </div>
                    <button
                      className="perm-row-remove"
                      onClick={() => removeMember(m.userId)}
                      disabled={isOwner}
                      title={isOwner ? "Owner can't be removed" : "Remove access"}
                    >
                      <IcClose size={13} />
                    </button>
                  </div>
                );
              })}
            </div>
          </div>

          {/* Link sharing */}
          <div className="perm-section">
            <div className="perm-h">General access</div>
            <div className="perm-access">
              {[
                { id: "restricted", title: "Restricted",       sub: "Only invited people can find this folder",  icon: <IcShield size={15} /> },
                { id: "workspace",  title: "Workspace",        sub: "Everyone in Vanday Studio can find it",   icon: <IcUsers size={15} /> },
                { id: "anyone",     title: "Anyone with link", sub: "External link share — no account needed",  icon: <IcExternal size={15} /> },
              ].map((opt) => (
                <button
                  key={opt.id}
                  className={`perm-access-row ${link === opt.id ? "is-on" : ""}`}
                  onClick={() => setLink(opt.id)}
                >
                  <span className="perm-access-ic">{opt.icon}</span>
                  <span style={{ flex: 1, minWidth: 0 }}>
                    <span className="perm-access-title">{opt.title}</span>
                    <span className="perm-access-sub">{opt.sub}</span>
                  </span>
                  {link === opt.id && <IcCheck size={14} style={{ color: "var(--accent)" }} />}
                </button>
              ))}
            </div>
          </div>

          {/* Inheritance hint */}
          <div className="perm-section perm-inherit">
            <IcLink size={12} />
            Inherits from <strong>Vanday Studio · Library</strong>. Sub-folders inside <strong>{folder.name}</strong> will inherit these permissions unless overridden.
          </div>
        </div>

        <div className="modal-foot perm-foot">
          <div style={{ fontSize: 12, color: "var(--text-faint)" }}>
            <IcInfo size={11} style={{ verticalAlign: "middle", marginRight: 4 }} />
            Permission changes are recorded in the activity audit log.
          </div>
          <div style={{ display: "flex", gap: 8 }}>
            <button className="btn" onClick={onClose}>Done</button>
          </div>
        </div>
      </div>
    </div>
  );
}

// ============================================================
// ShareModal — GP-02
// ============================================================
function ShareModal({ folder, asset, presetAssets, onClose, onOpenPortal }) {
  // Determine the candidate assets and default selection.
  //   - presetAssets (e.g. from bulk-select) → use that exact list.
  //   - asset (single-preview share) → the asset + its related links.
  //   - folder (folder-level share)  → every asset in that folder.
  const candidates = React.useMemo(() => {
    if (Array.isArray(presetAssets) && presetAssets.length > 0) {
      return presetAssets;
    }
    if (asset) {
      const relatedIds = (_relatedStore[asset.id] || getRelated(asset.id)).map((r) => r.id);
      const relatedAssets = relatedIds
        .map((id) => ASSETS.find((a) => a.id === id))
        .filter(Boolean);
      return [asset, ...relatedAssets];
    }
    return ASSETS.filter((a) => !folder || a.folder === folder.id);
  }, [presetAssets, asset, folder]);

  // Default selection:
  //   - single-asset share → ONLY the source asset. Related assets are shown
  //     as cards the user can opt into, but aren't included automatically.
  //   - folder / bulk share → everything in the candidate list.
  const [picked, setPicked] = React.useState(
    () => new Set(asset ? [asset.id] : candidates.map((a) => a.id))
  );
  const togglePick = (id) => setPicked((s) => {
    const n = new Set(s);
    n.has(id) ? n.delete(id) : n.add(id);
    return n;
  });
  const pickAll = () => setPicked(new Set(candidates.map((a) => a.id)));
  const pickNone = () => setPicked(new Set(asset ? [asset.id] : []));

  const [name, setName] = React.useState(
    Array.isArray(presetAssets) && presetAssets.length > 0
      ? `${presetAssets.length} asset${presetAssets.length === 1 ? "" : "s"} — review`
      : asset ? `${asset.name.replace(/\.[^.]+$/, "")} — review`
              : (folder ? `${folder.name} — client review` : "Share portal")
  );
  const [password, setPassword] = React.useState(true);
  const [passwordVal, setPasswordVal] = React.useState("");
  const [showPw, setShowPw] = React.useState(false);
  // Default expiry: 14 days out (computed, not a hardcoded date that goes stale).
  const [expiry, setExpiry] = React.useState(() => {
    const d = new Date();
    d.setDate(d.getDate() + 14);
    return d.toISOString().slice(0, 10);
  });
  const [allowDownload, setAllowDownload] = React.useState(true);
  const [watermark, setWatermark] = React.useState(false);
  const [brandColor, setBrandColor] = React.useState("oklch(0.58 0.16 32)");

  // "Previous portals" view — a list of every portal this workspace has made,
  // so people can find an old link, re-copy it, or revoke it.
  const [showPrevious, setShowPrevious] = React.useState(false);
  const [prevList, setPrevList] = React.useState(null); // null = not loaded yet
  const [prevBusy, setPrevBusy] = React.useState(false);
  const [prevCopied, setPrevCopied] = React.useState("");
  const loadPrevious = async () => {
    setPrevBusy(true);
    try {
      const r = await VandayAPI.listShares();
      setPrevList(r.shares || []);
    } catch { setPrevList([]); }
    setPrevBusy(false);
  };
  const openPrevious = () => { setShowPrevious(true); if (prevList === null) loadPrevious(); };
  const copyPortalLink = async (token) => {
    try {
      await navigator.clipboard.writeText(`${window.location.origin}/portal.html?t=${token}`);
      setPrevCopied(token);
      setTimeout(() => setPrevCopied(""), 1500);
    } catch {}
  };
  const revokePortal = async (token) => {
    if (!window.confirm("Revoke this portal? The link will stop working for anyone who has it.")) return;
    try {
      await VandayAPI.deleteShare(token);
      setPrevList((list) => (list || []).filter((s) => s.token !== token));
    } catch {}
  };

  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]);

  // Real portal URL — gets replaced with the server-issued one after Create.
  const [createdShare, setCreatedShare] = React.useState(null);
  const portalUrl = createdShare
    ? `${window.location.host}${createdShare.url}`
    : `vanday.com/p/${name.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 28)}-${Math.random().toString(36).slice(2, 6)}`;

  const confirm = async () => {
    if (createdShare) {
      window.open(createdShare.url, "_blank", "noopener");
      return;
    }
    const pickedAssets = Array.from(picked)
      .map((id) => candidates.find((a) => a.id === id))
      .filter((a) => a && a.isServer && !a.isServerVariant);
    if (pickedAssets.length === 0) {
      window.alert("Pick at least one uploaded asset — sample images can't be shared via real portal links.");
      return;
    }
    const expiryDays = Math.max(
      1,
      Math.ceil((new Date(expiry).getTime() - Date.now()) / (24 * 60 * 60 * 1000)),
    );
    try {
      const r = await VandayAPI.createShare({
        name,
        assetIds: pickedAssets.map((a) => a.id),
        password: password ? passwordVal : "",
        expiryDays,
        allowDownload,
        watermark,
        brandColor,
      });
      setCreatedShare(r);
      // Open the real portal in a new tab so the user can see what visitors see.
      window.open(r.url, "_blank", "noopener");
    } catch (err) {
      window.alert("Could not create portal: " + err.message);
    }
  };

  const [copied, setCopied] = React.useState("");
  const flagCopied = (what) => { setCopied(what); setTimeout(() => setCopied(""), 1500); };
  const copyUrl = async () => {
    if (!createdShare) return;
    const full = `${window.location.origin}${createdShare.url}`;
    try { await navigator.clipboard.writeText(full); flagCopied("url"); } catch {}
  };
  const copyPassword = async () => {
    if (!passwordVal) return;
    try { await navigator.clipboard.writeText(passwordVal); flagCopied("pw"); } catch {}
  };

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal share-modal" onClick={(e) => e.stopPropagation()}>
        <div className="modal-head">
          <div>
            <div style={{ display: "inline-flex", alignItems: "center", gap: 8, fontSize: 11, textTransform: "uppercase", letterSpacing: "0.06em", color: "var(--text-faint)", fontWeight: 500, marginBottom: 4 }}>
              <IcExternal size={11} /> Share portal
            </div>
            <h2 className="modal-title">{showPrevious ? "Previous portals" : "Create a portal"}</h2>
            <div style={{ fontSize: 12.5, color: "var(--text-muted)", marginTop: 4 }}>
              {showPrevious
                ? "Portals this workspace has shared. Copy a link again, or revoke one to stop it working."
                : "A branded, password-protected page for sharing with people outside your workspace."}
            </div>
          </div>
          <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
            <button className="btn ghost sm" onClick={showPrevious ? () => setShowPrevious(false) : openPrevious}>
              {showPrevious ? "← Back" : "Previous portals"}
            </button>
            <button className="btn ghost sm" onClick={onClose}><IcClose size={14} /></button>
          </div>
        </div>

        {showPrevious ? (
          <div className="share-body" style={{ display: "block", overflowY: "auto", padding: 4 }}>
            {prevBusy && <div style={{ padding: 24, textAlign: "center", color: "var(--text-muted)", fontSize: 13 }}>Loading…</div>}
            {!prevBusy && prevList && prevList.length === 0 && (
              <div style={{ padding: 40, textAlign: "center", color: "var(--text-faint)", fontSize: 13 }}>
                <div style={{ marginBottom: 6 }}><IcExternal size={20} style={{ color: "var(--text-faint)" }} /></div>
                No portals yet. Create one to share assets outside your workspace.
              </div>
            )}
            {!prevBusy && prevList && prevList.map((s) => {
              const expired = s.expiresAt && Date.now() > s.expiresAt;
              return (
                <div key={s.token} style={{ display: "flex", alignItems: "center", gap: 12, padding: "12px 14px", borderBottom: "1px solid var(--border)" }}>
                  <div style={{ minWidth: 0, flex: 1 }}>
                    <div style={{ fontWeight: 600, fontSize: 13.5, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
                      {s.name || "Untitled portal"}
                      {expired && <span style={{ marginLeft: 8, fontSize: 11, color: "var(--accent, #c8553d)", fontWeight: 500 }}>Expired</span>}
                    </div>
                    <div style={{ fontSize: 11.5, color: "var(--text-muted)", marginTop: 2 }}>
                      {s.assetCount} asset{s.assetCount === 1 ? "" : "s"}
                      {s.requiresPassword ? " · password" : ""}
                      {" · "}{s.viewCount} view{s.viewCount === 1 ? "" : "s"}
                      {s.expiresAt ? ` · expires ${new Date(s.expiresAt).toLocaleDateString("en-US", { month: "short", day: "numeric" })}` : ""}
                    </div>
                  </div>
                  <button className="btn sm" onClick={() => copyPortalLink(s.token)}>
                    {prevCopied === s.token ? "Copied!" : "Copy link"}
                  </button>
                  <button className="btn ghost sm" onClick={() => revokePortal(s.token)} title="Revoke portal" style={{ color: "var(--accent, #c8553d)" }}>
                    Revoke
                  </button>
                </div>
              );
            })}
          </div>
        ) : (
        <div className="share-body">
          {/* Left: settings */}
          <div className="share-settings">
            <div className="share-kv">
              <label>Portal name</label>
              <input
                className="up-text"
                value={name}
                onChange={(e) => setName(e.target.value)}
              />
            </div>

            <div className="share-kv">
              <label>Public URL</label>
              <div className="share-url">
                <span>{createdShare ? `http://${portalUrl}` : `https://${portalUrl}`}</span>
                <button className="btn sm" onClick={copyUrl} disabled={!createdShare}>
                  {copied === "url" ? "Copied!" : createdShare ? "Copy" : "Copy (after create)"}
                </button>
              </div>
            </div>

            <div className="share-section-h">Access</div>
            <button className="share-toggle-row" onClick={() => setPassword((v) => !v)}>
              <div>
                <div className="share-toggle-h"><IcShield size={12} /> Password protect</div>
                <div className="share-toggle-sub">Require a password to view the portal</div>
              </div>
              <span className={`toggle ${password ? "on" : ""}`} />
            </button>
            {password && (
              <div style={{ position: "relative", marginTop: -4, marginBottom: 12 }}>
                <input
                  className="up-text"
                  type={showPw ? "text" : "password"}
                  style={{ paddingRight: 108 }}
                  placeholder="Set a password"
                  value={passwordVal}
                  onChange={(e) => setPasswordVal(e.target.value)}
                />
                <button
                  type="button"
                  onClick={copyPassword}
                  disabled={!passwordVal}
                  title="Copy password"
                  style={{ position: "absolute", right: 52, top: "50%", transform: "translateY(-50%)", background: "none", border: "none", cursor: passwordVal ? "pointer" : "default", color: "var(--text-muted)", fontSize: 12, fontWeight: 500, padding: 4, opacity: passwordVal ? 1 : 0.4 }}
                >
                  {copied === "pw" ? "Copied!" : "Copy"}
                </button>
                <button
                  type="button"
                  onClick={() => setShowPw((v) => !v)}
                  title={showPw ? "Hide password" : "Show password"}
                  style={{ position: "absolute", right: 8, top: "50%", transform: "translateY(-50%)", background: "none", border: "none", cursor: "pointer", color: "var(--text-muted)", fontSize: 12, fontWeight: 500, padding: 4 }}
                >
                  {showPw ? "Hide" : "Show"}
                </button>
              </div>
            )}
            <div className="share-kv">
              <label><IcCalendar size={11} /> Expires on</label>
              <input
                className="up-text"
                type="date"
                value={expiry}
                min={new Date().toISOString().slice(0, 10)}
                onChange={(e) => setExpiry(e.target.value)}
              />
            </div>

            <div className="share-section-h">Asset behavior</div>
            <button className="share-toggle-row" onClick={() => setAllowDownload((v) => !v)}>
              <div>
                <div className="share-toggle-h"><IcDownload size={12} /> Allow downloads</div>
                <div className="share-toggle-sub">Visitors can download originals</div>
              </div>
              <span className={`toggle ${allowDownload ? "on" : ""}`} />
            </button>
            <button className="share-toggle-row" onClick={() => setWatermark((v) => !v)}>
              <div>
                <div className="share-toggle-h"><IcImage size={12} /> Watermark previews</div>
                <div className="share-toggle-sub">Diagonal watermark on view-only previews</div>
              </div>
              <span className={`toggle ${watermark ? "on" : ""}`} />
            </button>

          </div>

          {/* Right: preview — cards in here are the selection surface */}
          <div className="share-preview">
            <div className="share-preview-tabs">
              <div className="share-pick-summary">
                <div>
                  <div className="share-pick-h">Assets in this portal</div>
                  <div className="share-pick-sub">
                    Click a card to {asset ? "include / exclude" : "deselect"} it.{asset ? " Source asset is always included." : ""}
                  </div>
                </div>
                <div className="share-pick-actions">
                  <span className="share-pick-count">
                    <strong>{picked.size}</strong> of {candidates.length}
                  </span>
                  <button className="btn sm" onClick={pickAll}>All</button>
                  <button className="btn sm" onClick={pickNone}>{asset ? "Only this" : "None"}</button>
                </div>
              </div>
            </div>
            <div className="share-preview-shell" style={{ "--portal-accent": brandColor }}>
              <div className="share-preview-frame">
                <div className="portal-chrome-mini">
                  <div className="portal-chrome-dot" />
                  <div className="portal-chrome-dot" />
                  <div className="portal-chrome-dot" />
                  <div className="portal-chrome-url">{portalUrl}</div>
                </div>
                <div className="portal-mini">
                  <div className="portal-mini-head">
                    <div className="portal-mini-logo" style={{ background: brandColor }}>A</div>
                    <div>
                      <div className="portal-mini-h">{name}</div>
                      <div className="portal-mini-sub">
                        {picked.size} asset{picked.size === 1 ? "" : "s"} · expires {new Date(expiry).toLocaleDateString("en-US", { month: "short", day: "numeric" })}
                      </div>
                    </div>
                  </div>
                  <div className="portal-mini-grid">
                    {candidates.length === 0 && (
                      <div style={{ gridColumn: "1 / -1", padding: 20, textAlign: "center", fontSize: 11, color: "var(--text-faint)" }}>
                        Nothing to share.
                      </div>
                    )}
                    {candidates.map((a) => {
                      const isSource = asset && a.id === asset.id;
                      const on = picked.has(a.id);
                      return (
                        <button
                          type="button"
                          key={a.id}
                          className={`portal-mini-card share-pick-card ${on ? "is-on" : "is-off"} ${isSource ? "is-source" : ""}`}
                          onClick={() => !isSource && togglePick(a.id)}
                          disabled={isSource}
                          title={isSource ? "Source asset — always included" : (on ? "Click to exclude" : "Click to include")}
                        >
                          <div className="portal-mini-thumb" style={isVideoAsset(a) ? undefined : { backgroundImage: `url(${a.url})` }}>
                            {isVideoAsset(a) && <PortalPlayBadge size={24} />}
                            {watermark && on && <div className="portal-watermark-mini">VANDAY · PREVIEW</div>}
                            <span className={`share-pick-check ${on ? "is-on" : ""}`}>
                              {on && <IcCheck size={11} />}
                            </span>
                            {isSource && <span className="share-pick-source-tag">Source</span>}
                            {!on && !isSource && <div className="share-pick-overlay" />}
                          </div>
                        </button>
                      );
                    })}
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
        )}

        <div className="modal-foot">
          <div />
          <div style={{ display: "flex", gap: 8 }}>
            <button className="btn" onClick={onClose}>{showPrevious ? "Close" : "Cancel"}</button>
            {!showPrevious && (
              <button className="btn accent" onClick={confirm} disabled={picked.size === 0}>
                {createdShare ? "Open portal" : "Create portal"}
              </button>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}

// ============================================================
// SharePortalPage — GP-02 external view
// ============================================================
function SharePortalPage({ portal, onClose }) {
  // Respect explicit assetIds (single-asset share) over folder selection.
  const assets = portal.assetIds && portal.assetIds.length
    ? portal.assetIds.map((id) => ASSETS.find((a) => a.id === id)).filter(Boolean)
    : ASSETS.filter((a) => !portal.folder || a.folder === portal.folder).slice(0, 12);

  return (
    <div className="portal-shell" style={{ "--portal-accent": portal.brandColor }}>
      <header className="portal-topbar">
        <div className="portal-brand">
          <div className="portal-logo">A</div>
          <span>Vanday Studio</span>
        </div>
        <div className="portal-meta">
          <span><IcShield size={11} /> Password protected</span>
          <span className="dot" />
          <span><IcCalendar size={11} /> Expires {new Date(portal.expiry).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}</span>
          <span className="dot" />
          <button className="btn sm" onClick={onClose}>
            <IcClose size={12} /> Close preview
          </button>
        </div>
      </header>

      <main className="portal-main">
        <section className="portal-hero">
          <div>
            <div className="portal-pill">Shared with you</div>
            <h1 className="portal-h">{portal.name}</h1>
            <p className="portal-sub">
              {assets.length} asset{assets.length === 1 ? "" : "s"} shared by {(window.VandaySession.getActiveUser() || { name: "Vanday user" }).name} from Vanday Studio.
              {portal.download ? " Click any image to view full size, or download the originals below." : " Click any image to view full size."}
            </p>
          </div>
          <div className="portal-progress">
            <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
              <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12.5, color: "var(--text-muted)" }}>
                <span>Assets</span>
                <strong style={{ color: "var(--text)", fontFamily: "var(--font-mono)" }}>{assets.length}</strong>
              </div>
              <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12.5, color: "var(--text-muted)" }}>
                <span>Expires</span>
                <strong style={{ color: "var(--text)", fontFamily: "var(--font-mono)" }}>
                  {new Date(portal.expiry).toLocaleDateString("en-US", { month: "short", day: "numeric" })}
                </strong>
              </div>
              <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12.5, color: "var(--text-muted)" }}>
                <span>Downloads</span>
                <strong style={{ color: "var(--text)", fontFamily: "var(--font-mono)" }}>
                  {portal.download ? "Allowed" : "Disabled"}
                </strong>
              </div>
            </div>
            {portal.download && (
              <button className="btn portal-btn" style={{ marginTop: 14 }}>
                <IcDownload size={13} /> Download all (.zip)
              </button>
            )}
          </div>
        </section>

        <section className="portal-grid">
          {assets.map((a) => (
            <article key={a.id} className="portal-card">
              <div className="portal-card-thumb">
                {isVideoAsset(a)
                  ? <PortalPlayBadge size={44} />
                  : <img src={a.url} alt={a.name} loading="lazy" />}
                {portal.watermark && (
                  <>
                    <div className="portal-watermark">VANDAY STUDIO · PREVIEW</div>
                    <div className="portal-watermark wm-2">VANDAY STUDIO · PREVIEW</div>
                  </>
                )}
              </div>
              <div className="portal-card-body">
                <div className="portal-card-name">{a.name}</div>
                <div className="portal-card-meta">
                  {a.tags.slice(0, 3).map((t) => <span key={t} className="portal-tag">{t}</span>)}
                </div>
                {portal.download && (
                  <div className="portal-card-actions">
                    <button className="portal-action"><IcDownload size={12} /> Download original</button>
                  </div>
                )}
              </div>
            </article>
          ))}
        </section>
      </main>

      <footer className="portal-foot">
        <span>Powered by <strong>Vanday</strong></span>
        <span>This portal is private. Don't share the link.</span>
      </footer>
    </div>
  );
}

// ============================================================
// AuditLogSection — GP-11
// ============================================================
const ACTION_META = {
  "login":         { label: "Signed in",            color: "oklch(0.6 0.005 80)",  icon: <IcUsers size={12} /> },
  "upload":        { label: "Uploaded",              color: "oklch(0.62 0.16 235)",  icon: <IcUpload size={12} /> },
  "publish":       { label: "Published",             color: "oklch(0.62 0.15 145)",  icon: <IcSend size={12} /> },
  "publish.fail":  { label: "Publish failed",        color: "oklch(0.55 0.2 24)",    icon: <IcSend size={12} /> },
  "permission":    { label: "Permission change",     color: "oklch(0.55 0.14 290)",  icon: <IcShield size={12} /> },
  "delete":        { label: "Deleted",                color: "oklch(0.55 0.2 24)",   icon: <IcTrash size={12} /> },
  "restore":       { label: "Restored",               color: "oklch(0.62 0.15 145)", icon: <IcCheck size={12} /> },
  "share.create":  { label: "Share portal created",   color: "oklch(0.58 0.16 32)",  icon: <IcExternal size={12} /> },
  "share.view":    { label: "Portal viewed",          color: "oklch(0.66 0.06 80)",  icon: <IcEye size={12} /> },
  "plan.change":   { label: "Plan changed",           color: "oklch(0.55 0.16 70)",  icon: <IcCard size={12} /> },
  "integration":   { label: "Integration",            color: "oklch(0.55 0.14 220)", icon: <IcLink size={12} /> },
  "expire.set":    { label: "Expiry set",             color: "oklch(0.62 0.14 80)",  icon: <IcCalendar size={12} /> },
  "invite.accept": { label: "Joined workspace",       color: "oklch(0.55 0.14 155)", icon: <IcPlus size={12} /> },
};

function AuditLogSection() {
  const [actor, setActor] = React.useState("all");
  const [action, setAction] = React.useState("all");
  const [range, setRange] = React.useState("7d");
  const [query, setQuery] = React.useState("");

  let rows = AUDIT_LOG;
  if (actor !== "all") rows = rows.filter((r) => r.actor === actor);
  if (action !== "all") rows = rows.filter((r) => r.action === action);
  if (query) {
    const q = query.toLowerCase();
    rows = rows.filter((r) =>
      (r.target || "").toLowerCase().includes(q) ||
      (USERS.find((u) => u.id === r.actor)?.name || "").toLowerCase().includes(q) ||
      r.action.toLowerCase().includes(q)
    );
  }

  return (
    <>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-end" }}>
        <div>
          <h1 className="settings-h1">Audit log</h1>
          <p className="settings-sub">
            Every meaningful action in this workspace. Retained for 1 year on Team plan, unlimited on Business.
          </p>
        </div>
        <div style={{ display: "flex", gap: 8 }}>
          <button className="btn"><IcDownload size={13} /> Export CSV</button>
        </div>
      </div>

      <div className="section-card" style={{ padding: 0, overflow: "hidden" }}>
        <div className="audit-toolbar">
          <div className="search" style={{ width: 280, background: "var(--surface-soft)" }}>
            <IcSearch size={14} />
            <input
              placeholder="Search by actor, target, or action…"
              value={query}
              onChange={(e) => setQuery(e.target.value)}
            />
          </div>
          <select className="role-select audit-select" value={actor} onChange={(e) => setActor(e.target.value)}>
            <option value="all">All actors</option>
            {USERS.map((u) => <option key={u.id} value={u.id}>{u.name}</option>)}
          </select>
          <select className="role-select audit-select" value={action} onChange={(e) => setAction(e.target.value)}>
            <option value="all">All actions</option>
            {Object.entries(ACTION_META).map(([id, m]) => <option key={id} value={id}>{m.label}</option>)}
          </select>
          <select className="role-select audit-select" value={range} onChange={(e) => setRange(e.target.value)}>
            <option value="24h">Last 24 hours</option>
            <option value="7d">Last 7 days</option>
            <option value="30d">Last 30 days</option>
            <option value="all">All time</option>
          </select>
          <span style={{ marginLeft: "auto", fontSize: 12, color: "var(--text-muted)" }}>
            {rows.length} event{rows.length === 1 ? "" : "s"}
          </span>
        </div>

        <div className="audit-list">
          {rows.map((r) => {
            const u = USERS.find((x) => x.id === r.actor);
            const meta = ACTION_META[r.action] || { label: r.action, color: "var(--text-muted)", icon: <IcInfo size={12} /> };
            return (
              <div className="audit-row" key={r.id}>
                <div className="audit-time">{r.at}</div>
                <div className="audit-actor">
                  <span className="av" style={{
                    background: u?.color || "var(--surface-sunken)",
                    width: 24, height: 24, fontSize: 10,
                    borderRadius: "50%", color: "white",
                    display: "inline-grid", placeItems: "center", fontWeight: 600,
                  }}>
                    {u ? u.name.split(" ").map((s) => s[0]).join("") : "?"}
                  </span>
                  <div>
                    <div className="audit-actor-name">{u?.name || "Unknown"}</div>
                    <div className="audit-actor-email">{u?.email}</div>
                  </div>
                </div>
                <div className="audit-action">
                  <span className="audit-pill" style={{ color: meta.color, borderColor: "currentColor", background: "transparent" }}>
                    {meta.icon}
                    {meta.label}
                  </span>
                </div>
                <div className="audit-target">{r.target || <span style={{ color: "var(--text-faint)" }}>—</span>}</div>
                <div className="audit-ip">
                  <div style={{ fontFamily: "var(--font-mono)" }}>{r.ip}</div>
                  <div style={{ fontSize: 11, color: "var(--text-faint)" }}>{r.device}</div>
                </div>
              </div>
            );
          })}
        </div>
      </div>
    </>
  );
}

// ============================================================
// ExpiryField — FT-15 inline block for asset preview
// ============================================================
function ExpiryField({ asset }) {
  const e = asset.expiry;
  const [editing, setEditing] = React.useState(false);

  if (!e && !editing) {
    return (
      <div className="field-block">
        <div className="field-label">
          <IcCalendar size={11} /> Expiration
        </div>
        <button
          className="btn sm"
          style={{ borderStyle: "dashed", color: "var(--text-muted)" }}
          onClick={() => setEditing(true)}
        >
          <IcPlus size={11} /> Set expiry date
        </button>
      </div>
    );
  }

  return (
    <div className="field-block">
      <div className="field-label">
        <IcCalendar size={11} /> Expiration
        <span className="tag" style={{ marginLeft: "auto", fontSize: 9.5,
          background: e?.status === "expired" ? "oklch(0.95 0.04 24)" : e?.status === "soon" ? "oklch(0.95 0.06 80)" : "var(--surface-sunken)",
          color:      e?.status === "expired" ? "oklch(0.5 0.2 24)" : e?.status === "soon" ? "oklch(0.5 0.16 80)" : "var(--text-muted)",
        }}>
          {e?.status === "expired" ? "EXPIRED" : e?.status === "soon" ? `${e.daysLeft}d LEFT` : "OK"}
        </span>
      </div>
      <div className="expiry-block">
        <div className="expiry-row">
          <span style={{ color: "var(--text-muted)", fontSize: 12 }}>Expires</span>
          <span style={{ fontFamily: "var(--font-mono)", fontSize: 12.5 }}>{e?.date || "Not set"}</span>
        </div>
        <div className="expiry-row">
          <span style={{ color: "var(--text-muted)", fontSize: 12 }}>License</span>
          <span style={{ fontSize: 12.5 }}>{e?.license || "Not set"}</span>
        </div>
        <div className="expiry-row">
          <span style={{ color: "var(--text-muted)", fontSize: 12 }}>Take down on expiry</span>
          <span className={`toggle ${e?.takedown ? "on" : ""}`} />
        </div>
        {e?.status === "expired" && (
          <div className="expiry-note is-err">
            <IcInfo size={11} />
            This asset has expired. New publishes are blocked and active posts will be taken down on supported channels.
          </div>
        )}
        {e?.status === "soon" && (
          <div className="expiry-note">
            <IcInfo size={11} />
            Notifies the owner 14, 7, and 1 days before expiry. Active posts will be taken down automatically.
          </div>
        )}
      </div>
    </div>
  );
}

Object.assign(window, {
  PermissionsModal,
  RoleSelect,
  ShareModal,
  SharePortalPage,
  AuditLogSection,
  ExpiryField,
});
