// Vanday DAM — Wave 3: integrations & developer surface
//   • IntegrationMark    — small lettermark glyph (RD-04/05/06)
//   • IntegrationsSection — settings panel listing all integrations
//   • IntegrationDetail   — drawer for a single connected integration
//   • ApiSection          — API keys + webhooks (GP-15)
//   • SendToMenu          — preview dropdown
//   • SendToModal         — target-picker for the chosen destination

// Generic lettermark — same vocabulary as SiteMark but for integrations.
function IntegrationMark({ integration, size = 18 }) {
  return (
    <span
      className="int-mark"
      style={{
        width: size,
        height: size,
        background: integration.color,
        color: "white",
        fontSize: size * 0.42,
      }}
    >
      {integration.short}
    </span>
  );
}

// ============================================================
// IntegrationsSection — unified entry point
// The page is now a single hub: connected integrations + locked / coming-soon
// integrations all live together. The feature flag no longer swaps between two
// different pages — it just controls which actions the hub exposes (the locked
// items become upsell triggers for users without integrations access).
// ============================================================
function IntegrationsSection() {
  return <IntegrationsHub />;
}

// ============================================================
// Legacy prototype IntegrationsSection (kept for reference, unused).
// ============================================================
function _LegacyIntegrationsSection() {
  const features = (typeof window.useVandayFeatures === "function")
    ? window.useVandayFeatures()
    : { integrations: true };
  if (!features.integrations) return <IntegrationsComingSoon />;

  const [filter, setFilter] = React.useState("all"); // all | connected | available
  const [active, setActive] = React.useState(null);  // integration object when detail is open
  const [search, setSearch] = React.useState("");

  let rows = INTEGRATIONS;
  if (filter !== "all") rows = rows.filter((i) => i.status === filter);
  if (search) {
    const q = search.toLowerCase();
    rows = rows.filter((i) => i.name.toLowerCase().includes(q) || (i.category || "").toLowerCase().includes(q));
  }
  const grouped = INTEGRATION_CATEGORIES.map((cat) => ({
    cat,
    items: rows.filter((i) => i.category === cat),
  })).filter((g) => g.items.length);

  const connectedCount = INTEGRATIONS.filter((i) => i.status === "connected").length;

  return (
    <>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-end" }}>
        <div>
          <h1 className="settings-h1">Integrations</h1>
          <p className="settings-sub">
            Connect Vanday to the tools you already use. <strong style={{ color: "var(--text)", fontWeight: 500 }}>{connectedCount}</strong> of {INTEGRATIONS.length} active.
          </p>
        </div>
        <div style={{ display: "flex", gap: 8 }}>
          <div className="search" style={{ width: 240, background: "var(--surface-soft)" }}>
            <IcSearch size={14} />
            <input
              placeholder="Search integrations…"
              value={search}
              onChange={(e) => setSearch(e.target.value)}
            />
          </div>
          <button className="btn"><IcExternal size={13} /> Browse marketplace</button>
        </div>
      </div>

      <div className="int-filters">
        {[
          { id: "all",       label: "All",        count: INTEGRATIONS.length },
          { id: "connected", label: "Connected",  count: INTEGRATIONS.filter((i) => i.status === "connected").length },
          { id: "available", label: "Available",  count: INTEGRATIONS.filter((i) => i.status === "available").length },
        ].map((f) => (
          <button
            key={f.id}
            className={`chip ${filter === f.id ? "active" : ""}`}
            onClick={() => setFilter(f.id)}
            style={{ cursor: "pointer" }}
          >
            {f.label}
            <span style={{
              fontFamily: "var(--font-mono)", fontSize: 10.5,
              marginLeft: 4, opacity: 0.7,
            }}>
              {f.count}
            </span>
          </button>
        ))}
      </div>

      {grouped.map(({ cat, items }) => (
        <div key={cat}>
          <div className="int-group-h">{cat}</div>
          <div className="int-grid">
            {items.map((i) => (
              <button key={i.id} className="int-card" onClick={() => setActive(i)}>
                <div className="int-card-head">
                  <IntegrationMark integration={i} size={36} />
                  <span className={`int-status int-status-${i.status}`}>
                    {i.status === "connected" ? (
                      <>
                        <span className="int-status-dot" />
                        Connected
                      </>
                    ) : "Connect"}
                  </span>
                </div>
                <div className="int-card-name">{i.name}</div>
                {i.blurb && <div className="int-card-blurb">{i.blurb}</div>}
                {i.status === "connected" && i.account && (
                  <div className="int-card-account">
                    <IcCheck size={11} /> {i.account}
                  </div>
                )}
              </button>
            ))}
          </div>
        </div>
      ))}

      {active && (
        <IntegrationDetail integration={active} onClose={() => setActive(null)} />
      )}
    </>
  );
}

// ============================================================
// IntegrationDetail — drawer for managing one integration
// ============================================================
function IntegrationDetail({ integration: i, onClose }) {
  const [tab, setTab] = React.useState("overview");

  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 isConnected = i.status === "connected";

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal int-modal" onClick={(e) => e.stopPropagation()}>
        <div className="modal-head" style={{ alignItems: "center" }}>
          <div style={{ display: "flex", alignItems: "center", gap: 14 }}>
            <IntegrationMark integration={i} size={44} />
            <div>
              <h2 className="modal-title">{i.name}</h2>
              <div style={{ fontSize: 12.5, color: "var(--text-muted)", marginTop: 2, display: "inline-flex", alignItems: "center", gap: 8 }}>
                {isConnected ? (
                  <>
                    <span className="int-status int-status-connected" style={{ padding: "2px 8px" }}>
                      <span className="int-status-dot" />
                      Connected
                    </span>
                    <span>{i.account}</span>
                    <span style={{ color: "var(--text-faint)" }}>·</span>
                    <span>Since {i.connectedAt}</span>
                  </>
                ) : (
                  <span>{i.category} integration</span>
                )}
              </div>
            </div>
          </div>
          <button className="btn ghost sm" onClick={onClose}><IcClose size={14} /></button>
        </div>

        {isConnected ? (
          <>
            <div className="int-tabs">
              {["overview", "settings", "events"].map((t) => (
                <button
                  key={t}
                  className={`pub-tab ${tab === t ? "active" : ""}`}
                  onClick={() => setTab(t)}
                  style={{ textTransform: "capitalize" }}
                >
                  {t}
                </button>
              ))}
            </div>

            <div className="int-body">
              {tab === "overview" && (
                <>
                  <p style={{ fontSize: 13.5, color: "var(--text)", lineHeight: 1.6, marginTop: 0 }}>
                    {i.blurb}
                  </p>
                  {i.capabilities && (
                    <>
                      <div className="int-section-h">What this integration does</div>
                      <ul className="int-caps">
                        {i.capabilities.map((c) => (
                          <li key={c}>
                            <IcCheck size={12} /> {c}
                          </li>
                        ))}
                      </ul>
                    </>
                  )}
                  <div className="int-section-h">Recent activity</div>
                  <div className="int-activity">
                    {AUDIT_LOG.filter((a) => a.action === "integration" || (a.target || "").toLowerCase().includes(i.name.toLowerCase()))
                      .slice(0, 4)
                      .map((a) => (
                        <div className="int-activity-row" key={a.id}>
                          <span className="int-activity-dot" />
                          <div style={{ flex: 1, minWidth: 0 }}>
                            <div style={{ fontSize: 12.5 }}>{a.target}</div>
                            <div style={{ fontSize: 11, color: "var(--text-faint)", fontFamily: "var(--font-mono)" }}>{a.at}</div>
                          </div>
                        </div>
                      ))}
                  </div>
                </>
              )}

              {tab === "settings" && (
                <>
                  <div className="row" style={{ borderTop: 0, paddingTop: 0 }}>
                    <div>
                      <div className="row-label">Account</div>
                      <div className="row-sub">{i.account}</div>
                    </div>
                    <button className="btn sm">Switch account</button>
                  </div>
                  <div className="row">
                    <div>
                      <div className="row-label">Two-way sync</div>
                      <div className="row-sub">Changes in Vanday propagate to {i.name}</div>
                    </div>
                    <button className="toggle on" />
                  </div>
                  <div className="row">
                    <div>
                      <div className="row-label">Apply tags to imported assets</div>
                      <div className="row-sub">Adds {i.short.toLowerCase()}-source tag</div>
                    </div>
                    <button className="toggle on" />
                  </div>
                  <div className="row" style={{ borderBottom: 0 }}>
                    <div>
                      <div className="row-label" style={{ color: "oklch(0.55 0.2 24)" }}>Disconnect {i.name}</div>
                      <div className="row-sub">Revokes OAuth token. Imported assets stay.</div>
                    </div>
                    <button className="btn" style={{ color: "oklch(0.55 0.2 24)", borderColor: "oklch(0.55 0.2 24)" }}>
                      Disconnect
                    </button>
                  </div>
                </>
              )}

              {tab === "events" && (
                <>
                  <div className="int-section-h">Subscribe to events on {i.name}</div>
                  {[
                    { id: "ev-1", label: "Asset replaced \u2192 update linked posts", on: true },
                    { id: "ev-2", label: "Asset deleted \u2192 flag in {i.name}",    on: true },
                    { id: "ev-3", label: "Expiry reached \u2192 take down content",   on: false },
                    { id: "ev-4", label: "Publish success \u2192 log to audit",       on: true },
                  ].map((e, idx) => (
                    <div className="row" key={e.id} style={idx === 0 ? { borderTop: 0, paddingTop: 0 } : undefined}>
                      <div className="row-label">{e.label.replace("{i.name}", i.name)}</div>
                      <button className={`toggle ${e.on ? "on" : ""}`} />
                    </div>
                  ))}
                </>
              )}
            </div>

            <div className="modal-foot" style={{ background: "var(--surface)" }}>
              <div style={{ fontSize: 12, color: "var(--text-faint)" }}>
                <IcInfo size={11} style={{ verticalAlign: "middle", marginRight: 4 }} />
                Last sync 2 minutes ago.
              </div>
              <div style={{ display: "flex", gap: 8 }}>
                <button className="btn" onClick={onClose}>Close</button>
                <button className="btn primary">Save changes</button>
              </div>
            </div>
          </>
        ) : (
          <>
            <div className="int-body">
              <p style={{ fontSize: 13.5, color: "var(--text)", lineHeight: 1.6, marginTop: 0 }}>
                {i.blurb || `Connect Vanday to ${i.name} to streamline your workflow.`}
              </p>
              <div className="int-connect-card">
                <IntegrationMark integration={i} size={56} />
                <div style={{ marginTop: 14 }}>
                  <strong style={{ fontSize: 15, fontWeight: 500 }}>Connect to {i.name}</strong>
                  <div style={{ fontSize: 12.5, color: "var(--text-muted)", marginTop: 6 }}>
                    You'll be redirected to {i.name} to authorize Vanday. We request only the scopes needed.
                  </div>
                </div>
              </div>
            </div>
            <div className="modal-foot" style={{ background: "var(--surface)" }}>
              <div style={{ fontSize: 12, color: "var(--text-faint)" }}>
                Powered by OAuth 2.0
              </div>
              <div style={{ display: "flex", gap: 8 }}>
                <button className="btn" onClick={onClose}>Cancel</button>
                <button className="btn accent">
                  <IcExternal size={13} /> Continue to {i.name}
                </button>
              </div>
            </div>
          </>
        )}
      </div>
    </div>
  );
}

// ============================================================
// ApiSection — keys + webhooks (GP-15)
// ============================================================
function ApiSection() {
  const [keys, setKeys] = React.useState(API_KEYS);
  const [hooks, setHooks] = React.useState(WEBHOOKS);
  const [creatingHook, setCreatingHook] = React.useState(false);
  const [hookUrl, setHookUrl] = React.useState("");
  const [hookEvents, setHookEvents] = React.useState(new Set(["asset.created", "publish.success"]));

  const toggleHookEvent = (e) => setHookEvents((s) => {
    const n = new Set(s);
    n.has(e) ? n.delete(e) : n.add(e);
    return n;
  });
  const addHook = () => {
    if (!hookUrl) return;
    setHooks((h) => [
      { id: `w-${Date.now()}`, url: hookUrl, events: Array.from(hookEvents), status: "healthy", last: "—" },
      ...h,
    ]);
    setHookUrl("");
    setCreatingHook(false);
  };
  const revokeKey = (id) => setKeys((ks) => ks.map((k) => (k.id === id ? { ...k, revoked: true } : k)));

  const codeSample = `curl https://api.vanday.com/v1/assets \\
  -H "Authorization: Bearer ap_live_8f2a…" \\
  -H "Content-Type: application/json"`;

  return (
    <>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-end" }}>
        <div>
          <h1 className="settings-h1">API & webhooks</h1>
          <p className="settings-sub">
            Programmatic access to assets, folders, search and publish events. Available on the Business plan.
          </p>
        </div>
        <button className="btn"><IcExternal size={13} /> API docs</button>
      </div>

      {/* Quick code sample */}
      <div className="section-card api-sample">
        <div className="api-sample-head">
          <span style={{ fontSize: 11, textTransform: "uppercase", letterSpacing: "0.06em", color: "var(--text-faint)", fontWeight: 500 }}>
            Authenticated request
          </span>
          <button className="btn sm">Copy</button>
        </div>
        <pre className="api-sample-code">{codeSample}</pre>
        <div className="api-sample-meta">
          REST + GraphQL endpoints · 1,000 req/min rate limit · OpenAPI spec available
        </div>
      </div>

      {/* API keys */}
      <div className="section-card" style={{ padding: 0, overflow: "hidden" }}>
        <div className="api-card-head">
          <div>
            <h2 className="section-h" style={{ margin: 0 }}>API keys</h2>
            <div style={{ fontSize: 12.5, color: "var(--text-muted)", marginTop: 2 }}>
              {keys.filter((k) => !k.revoked).length} active · {keys.filter((k) => k.revoked).length} revoked
            </div>
          </div>
          <button className="btn accent"><IcPlus size={13} /> Generate key</button>
        </div>
        <table className="table">
          <thead>
            <tr>
              <th>Name</th>
              <th>Key</th>
              <th>Scopes</th>
              <th>Last used</th>
              <th>Created</th>
              <th></th>
            </tr>
          </thead>
          <tbody>
            {keys.map((k) => (
              <tr key={k.id} style={k.revoked ? { opacity: 0.5 } : undefined}>
                <td>{k.name}</td>
                <td style={{ fontFamily: "var(--font-mono)", fontSize: 12 }}>{k.prefix}…{k.revoked && <span style={{ marginLeft: 8, color: "oklch(0.55 0.2 24)" }}>Revoked</span>}</td>
                <td>
                  {k.scopes.map((s) => (
                    <span key={s} className="role-pill" style={{ marginRight: 4 }}>{s}</span>
                  ))}
                </td>
                <td style={{ color: "var(--text-muted)" }}>{k.lastUsed}</td>
                <td style={{ color: "var(--text-muted)", fontFamily: "var(--font-mono)", fontSize: 12 }}>{k.created}</td>
                <td style={{ textAlign: "right" }}>
                  {!k.revoked && (
                    <button className="btn sm" onClick={() => revokeKey(k.id)}>
                      Revoke
                    </button>
                  )}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      {/* Webhooks */}
      <div className="section-card" style={{ padding: 0, overflow: "hidden" }}>
        <div className="api-card-head">
          <div>
            <h2 className="section-h" style={{ margin: 0 }}>Webhooks</h2>
            <div style={{ fontSize: 12.5, color: "var(--text-muted)", marginTop: 2 }}>
              Push events to your endpoint. Retries with exponential backoff up to 24h.
            </div>
          </div>
          <button className="btn accent" onClick={() => setCreatingHook((v) => !v)}>
            <IcPlus size={13} /> {creatingHook ? "Cancel" : "Add webhook"}
          </button>
        </div>

        {creatingHook && (
          <div className="api-hook-create">
            <div className="api-hook-row">
              <label>Endpoint URL</label>
              <input
                className="up-text"
                placeholder="https://example.com/vanday-hook"
                value={hookUrl}
                onChange={(e) => setHookUrl(e.target.value)}
              />
            </div>
            <div className="api-hook-row">
              <label>Events to subscribe</label>
              <div className="api-hook-events">
                {WEBHOOK_EVENTS.map((e) => (
                  <button
                    key={e}
                    type="button"
                    className={`chip ${hookEvents.has(e) ? "active" : ""}`}
                    onClick={() => toggleHookEvent(e)}
                    style={{ fontFamily: "var(--font-mono)", cursor: "pointer" }}
                  >
                    {e}
                  </button>
                ))}
              </div>
            </div>
            <div className="api-hook-foot">
              <span style={{ fontSize: 11.5, color: "var(--text-faint)" }}>
                {hookEvents.size} event{hookEvents.size === 1 ? "" : "s"} selected
              </span>
              <div style={{ display: "flex", gap: 6 }}>
                <button className="btn sm" onClick={() => setCreatingHook(false)}>Cancel</button>
                <button className="btn accent" onClick={addHook} disabled={!hookUrl}>
                  Add webhook
                </button>
              </div>
            </div>
          </div>
        )}

        <div className="api-hook-list">
          {hooks.map((h) => (
            <div className="api-hook" key={h.id}>
              <span className={`api-hook-status is-${h.status}`}>
                <span className="api-hook-dot" />
                {h.status === "healthy" ? "Healthy" : "Failing"}
              </span>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div className="api-hook-url">{h.url}</div>
                <div className="api-hook-events">
                  {h.events.map((e) => (
                    <span key={e} className="role-pill" style={{ fontFamily: "var(--font-mono)" }}>{e}</span>
                  ))}
                </div>
              </div>
              <div style={{ fontSize: 11.5, color: "var(--text-faint)" }}>Last · {h.last}</div>
              <button className="btn ghost sm"><IcMore size={14} /></button>
            </div>
          ))}
        </div>
      </div>
    </>
  );
}

// ============================================================
// SendToMenu + SendToModal — preview action (RD-04/05/06)
// ============================================================
function SendToMenu({ asset, onSend }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);

  React.useEffect(() => {
    if (!open) return;
    const onClick = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
    window.addEventListener("mousedown", onClick);
    window.addEventListener("keydown", onKey);
    return () => {
      window.removeEventListener("mousedown", onClick);
      window.removeEventListener("keydown", onKey);
    };
  }, [open]);

  const sendable = INTEGRATIONS.filter((i) =>
    i.status === "connected" && (i.category === "Publishing" || i.category === "Design")
  );

  return (
    <div ref={ref} style={{ position: "relative" }}>
      <button className="btn" onClick={() => setOpen((v) => !v)}>
        <IcExternal size={14} /> Send to
        <IcChevD size={12} style={{ marginLeft: 2 }} />
      </button>
      {open && (
        <div className="send-menu">
          <div className="send-menu-h">Connected destinations</div>
          {sendable.map((i) => (
            <button
              key={i.id}
              className="send-menu-item"
              onClick={() => { setOpen(false); onSend(i); }}
            >
              <IntegrationMark integration={i} size={22} />
              <div style={{ flex: 1 }}>
                <div className="send-menu-name">{i.name}</div>
                <div className="send-menu-account">{i.account}</div>
              </div>
              <IcChevR size={12} style={{ color: "var(--text-faint)" }} />
            </button>
          ))}
          <div className="send-menu-divider" />
          <button className="send-menu-link">
            <IcPlus size={12} /> Connect another destination
          </button>
        </div>
      )}
    </div>
  );
}

// Per-destination target picker.
function SendToModal({ asset, integration, onClose }) {
  const [confirming, setConfirming] = React.useState(false);
  const [picked, setPicked] = React.useState(null);
  const [pos, setPos] = React.useState("primary");

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

  // Fake "targets" per integration — products / posts / files / designs.
  const targetSets = {
    shopify: {
      label: "Shopify product",
      hint: "Pick the product to attach this image to.",
      items: [
        { id: "p-101", name: "Studio Headphones \u2014 Onyx",      sub: "$249 · 12 variants · SKU SH-ONX-01" },
        { id: "p-102", name: "Studio Headphones \u2014 Sandstone", sub: "$249 · 12 variants · SKU SH-SND-01" },
        { id: "p-103", name: "Handmade ceramic bowl",                sub: "$48 · 4 variants · SKU CER-BWL-04" },
        { id: "p-104", name: "Skincare \u2014 hydrating serum",      sub: "$62 · 1 variant · SKU SKN-SER-01" },
        { id: "p-105", name: "Running shoes \u2014 Trail Pro",       sub: "$185 · 8 variants · SKU SHO-TRP-01" },
      ],
      positions: [
        { id: "primary",    label: "Primary image"    },
        { id: "additional", label: "Additional image" },
        { id: "variant",    label: "Variant image"    },
      ],
    },
    wordpress: {
      label: "WordPress post",
      hint: "Insert into the post's content block at the cursor.",
      items: [
        { id: "wp-201", name: "Spring '26 lookbook \u2014 launch announcement", sub: "Draft · last edited 2 h ago" },
        { id: "wp-202", name: "Behind the shoot: ceramics",                       sub: "Published Mar 12 · 12 min read" },
        { id: "wp-203", name: "Studio diaries: April",                            sub: "Scheduled May 22, 10:00 AM" },
        { id: "wp-204", name: "Press \u2014 Vanday Spring collection",         sub: "Draft · last edited yesterday" },
      ],
      positions: [
        { id: "featured", label: "Featured image" },
        { id: "inline",   label: "Inline at cursor" },
        { id: "gallery",  label: "Add to post gallery" },
      ],
    },
    figma: {
      label: "Figma file",
      hint: "Place onto the selected frame, or pick from your recent files.",
      items: [
        { id: "fg-301", name: "Web \u2014 Spring '26 landing", sub: "Edited just now · Vanday Studio team" },
        { id: "fg-302", name: "Mobile \u2014 onboarding flow",  sub: "Edited 2 h ago · Vanday Studio team" },
        { id: "fg-303", name: "Brand v3 \u2014 system",          sub: "Edited yesterday · Vanday Studio team" },
        { id: "fg-304", name: "Pitch deck \u2014 Series A",      sub: "Edited Apr 22 · Maya Rodriguez" },
      ],
      positions: [
        { id: "selected", label: "Place on selected frame" },
        { id: "newframe", label: "Create new frame"        },
        { id: "library",  label: "Add to team library"     },
      ],
    },
    canva: {
      label: "Canva design",
      hint: "Place into an existing design or start a new one.",
      items: [
        { id: "cv-401", name: "Instagram Story \u2014 May campaign", sub: "Story · 1080×1920" },
        { id: "cv-402", name: "Email header \u2014 spring drop",      sub: "Custom · 1200×400" },
      ],
      positions: [
        { id: "fit",     label: "Fit to frame"   },
        { id: "cover",   label: "Fill background" },
        { id: "library", label: "Add to brand kit"  },
      ],
    },
  };

  const config = targetSets[integration.id] || targetSets.shopify;

  if (confirming) {
    return (
      <div className="modal-backdrop" onClick={onClose}>
        <div className="modal send-modal send-modal-confirm" onClick={(e) => e.stopPropagation()}>
          <div className="send-confirm-body">
            <div className="send-confirm-ic"><IcCheck size={32} /></div>
            <h2 className="modal-title" style={{ fontSize: 20 }}>
              Sent to {integration.name}
            </h2>
            <p style={{ fontSize: 13.5, color: "var(--text-muted)", maxWidth: 380, textAlign: "center" }}>
              <span style={{ fontFamily: "var(--font-mono)" }}>{asset.name}</span> is now on{" "}
              <strong style={{ color: "var(--text)", fontWeight: 500 }}>{picked.name}</strong>
              {" "}({(config.positions.find((p) => p.id === pos) || {}).label}).
            </p>
            <div className="send-confirm-foot">
              <button className="btn" onClick={onClose}>Done</button>
              <button className="btn primary"><IcExternal size={13} /> Open in {integration.name}</button>
            </div>
          </div>
        </div>
      </div>
    );
  }

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal send-modal" onClick={(e) => e.stopPropagation()}>
        <div className="modal-head">
          <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
            <IntegrationMark integration={integration} size={32} />
            <div>
              <h2 className="modal-title">Send to {integration.name}</h2>
              <div style={{ fontSize: 12.5, color: "var(--text-muted)", marginTop: 2 }}>
                {config.hint}
              </div>
            </div>
          </div>
          <button className="btn ghost sm" onClick={onClose}><IcClose size={14} /></button>
        </div>

        <div className="send-body">
          {/* Asset preview */}
          <div className="send-asset">
            <div className="send-asset-thumb" style={{ backgroundImage: `url(${asset.url})` }} />
            <div style={{ minWidth: 0, flex: 1 }}>
              <div style={{ fontFamily: "var(--font-mono)", fontSize: 12.5 }}>{asset.name}</div>
              <div style={{ fontSize: 11.5, color: "var(--text-faint)", marginTop: 3 }}>
                {asset.w} × {asset.h} · {asset.size}
              </div>
            </div>
            <span className="send-arrow"><IcChevR size={16} /></span>
            <IntegrationMark integration={integration} size={32} />
          </div>

          {/* Target picker */}
          <div className="send-section-h">{config.label}</div>
          <div className="send-target-list">
            {config.items.map((t) => (
              <button
                key={t.id}
                className={`send-target ${picked?.id === t.id ? "is-on" : ""}`}
                onClick={() => setPicked(t)}
              >
                <span className={`send-target-radio ${picked?.id === t.id ? "is-on" : ""}`} />
                <div style={{ flex: 1, minWidth: 0, textAlign: "left" }}>
                  <div className="send-target-name">{t.name}</div>
                  <div className="send-target-sub">{t.sub}</div>
                </div>
              </button>
            ))}
          </div>

          {/* Position selector */}
          {config.positions && (
            <>
              <div className="send-section-h">Placement</div>
              <div className="send-positions">
                {config.positions.map((p) => (
                  <button
                    key={p.id}
                    className={`send-position ${pos === p.id ? "is-on" : ""}`}
                    onClick={() => setPos(p.id)}
                  >
                    {p.label}
                  </button>
                ))}
              </div>
            </>
          )}
        </div>

        <div className="modal-foot" style={{ background: "var(--surface)" }}>
          <div style={{ fontSize: 12, color: "var(--text-faint)" }}>
            <IcInfo size={11} style={{ verticalAlign: "middle", marginRight: 4 }} />
            Send events are recorded in the audit log.
          </div>
          <div style={{ display: "flex", gap: 8 }}>
            <button className="btn" onClick={onClose}>Cancel</button>
            <button
              className="btn accent"
              disabled={!picked}
              onClick={() => setConfirming(true)}
              style={!picked ? { opacity: 0.5, cursor: "not-allowed" } : undefined}
            >
              Send to {integration.name}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

// ============================================================
// ConnectedIntegrationsManager — lists every integration the current user
// has live connections for, with per-integration management (configure /
// disconnect). Hides itself when the user has nothing connected so we don't
// show an empty "Connected" header.
//
// Today this is just Google Drive. As we add Canva, Dropbox, etc., each one
// returns a row from /api/integrations and gets its own card here.
// ============================================================
function ConnectedIntegrationsManager() {
  // Gated to paid-tier (or beta accounts with the `integrations` override).
  // Free beta users still get the simpler Connect/Disconnect baked into the
  // GoogleDriveConnectCard below — they just don't see this richer manager.
  const features = (typeof window.useVandayFeatures === "function")
    ? window.useVandayFeatures()
    : { integrations: false };

  const [conns, setConns] = React.useState(null);
  const [error, setError] = React.useState(null);

  const authHeaders = async () => {
    try {
      const t = window.Clerk?.session ? await window.Clerk.session.getToken() : null;
      return t ? { Authorization: `Bearer ${t}` } : {};
    } catch { return {}; }
  };

  const load = React.useCallback(async () => {
    setError(null);
    try {
      const r = await fetch("/api/integrations", { headers: await authHeaders() });
      const body = await r.json();
      if (!r.ok) { setError(body.error || `Server returned ${r.status}`); return; }
      setConns(body.connections || []);
    } catch { setError("Couldn't reach the server."); }
  }, []);
  React.useEffect(() => { load(); }, [load]);

  // Provider metadata for rendering — extend as new integrations land.
  const PROVIDERS = {
    google_drive: {
      label: "Google Drive",
      mark: "DR",
      color: "oklch(0.55 0.16 60)",
      disconnectPath: "/api/integrations/google/disconnect",
      blurb: "Push photos and videos from Drive into Vanday.",
    },
  };

  // Gate AFTER hooks to keep React hooks-order stable.
  if (!features.integrations) return null;
  if (conns === null) return null; // first load
  if (conns.length === 0) return null; // nothing connected → don't render header

  const onDisconnect = async (provider) => {
    const meta = PROVIDERS[provider];
    if (!window.confirm(`Disconnect ${meta?.label || provider}? Imported assets stay; future imports stop.`)) return;
    try {
      const r = await fetch(meta.disconnectPath, { method: "POST", headers: await authHeaders() });
      if (!r.ok) throw new Error("server " + r.status);
      load();
    } catch { setError("Disconnect failed — try refreshing."); }
  };

  return (
    <>
      <div style={{ fontSize: 11, fontWeight: 500, textTransform: "uppercase", letterSpacing: "0.06em", color: "var(--text-faint)", marginBottom: 10 }}>
        Connected
      </div>
      <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
        {conns.map((c) => {
          const meta = PROVIDERS[c.provider] || { label: c.provider, mark: "??", color: "var(--text-faint)" };
          return (
            <div key={c.provider} className="section-card" style={{ padding: 0, overflow: "hidden" }}>
              <div style={{ display: "flex", alignItems: "center", gap: 14, padding: "16px 20px" }}>
                <div
                  aria-hidden
                  style={{
                    width: 36, height: 36, borderRadius: 8,
                    background: meta.color, color: "white",
                    display: "grid", placeItems: "center",
                    fontWeight: 700, fontSize: 12, flexShrink: 0,
                    fontFamily: "var(--font-mono)",
                  }}
                >{meta.mark}</div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontWeight: 600, fontSize: 14 }}>{meta.label}</div>
                  <div style={{ fontSize: 12, color: "var(--text-muted)", marginTop: 2 }}>
                    {c.external_email
                      ? <>Connected as <span style={{ fontFamily: "var(--font-mono)" }}>{c.external_email}</span></>
                      : "Connected"}
                    {c.updated_at ? (
                      <span style={{ marginLeft: 8, color: "var(--text-faint)" }}>
                        · last refreshed {new Date(c.updated_at).toLocaleDateString()}
                      </span>
                    ) : null}
                  </div>
                </div>
                <button
                  className="btn"
                  onClick={() => onDisconnect(c.provider)}
                  style={{ color: "oklch(0.55 0.2 24)", borderColor: "oklch(0.55 0.2 24)" }}
                >
                  Disconnect
                </button>
              </div>
            </div>
          );
        })}
      </div>
      {error && (
        <div style={{
          marginTop: 10, padding: "8px 12px", fontSize: 12.5,
          background: "oklch(0.96 0.04 24)", color: "oklch(0.42 0.16 24)",
          borderRadius: 8, border: "1px solid oklch(0.85 0.08 24)",
        }}>{error}</div>
      )}
    </>
  );
}

// ============================================================
// GoogleDriveConnectCard — the first real integration. Shows current
// connection status (queried from /api/integrations) and a Connect /
// Disconnect button. Navigates the browser to /api/integrations/google/connect
// which kicks off the OAuth dance and returns the user back here.
// ============================================================
function GoogleDriveConnectCard() {
  const [conn, setConn] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [error, setError] = React.useState(null);

  const authHeaders = async () => {
    try {
      const t = window.Clerk?.session ? await window.Clerk.session.getToken() : null;
      return t ? { Authorization: `Bearer ${t}` } : {};
    } catch { return {}; }
  };

  const load = React.useCallback(async () => {
    setLoading(true); setError(null);
    try {
      const r = await fetch("/api/integrations", { headers: await authHeaders() });
      const body = await r.json();
      if (!r.ok) { setError(body.error || `Server returned ${r.status}`); return; }
      setConn((body.connections || []).find((c) => c.provider === "google_drive") || null);
    } catch { setError("Couldn't reach the server."); }
    finally { setLoading(false); }
  }, []);
  React.useEffect(() => { load(); }, [load]);

  const onConnect = () => {
    // Browser navigation — Clerk cookies travel with the request so the
    // /connect route can identify the user before redirecting to Google.
    // Vanday is a hash-routed SPA, so we return to /#settings/integrations
    // (not /settings/integrations, which isn't an Express route).
    const ret = encodeURIComponent("/#settings/integrations");
    window.location.href = `/api/integrations/google/connect?return=${ret}`;
  };

  const onDisconnect = async () => {
    if (!window.confirm("Disconnect Google Drive? Imported assets stay; Vanday loses access to import more.")) return;
    try {
      const r = await fetch("/api/integrations/google/disconnect", {
        method: "POST", headers: await authHeaders(),
      });
      if (!r.ok) throw new Error("server " + r.status);
      load();
    } catch { setError("Disconnect failed — try refreshing."); }
  };

  return (
    <div className="section-card" style={{ padding: 0, overflow: "hidden" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 14, padding: "18px 20px" }}>
        <div
          aria-hidden
          style={{
            width: 40, height: 40, borderRadius: 9,
            background: "oklch(0.55 0.16 60)", color: "white",
            display: "grid", placeItems: "center",
            fontWeight: 700, fontSize: 13, flexShrink: 0,
            fontFamily: "var(--font-mono)",
          }}
        >DR</div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontWeight: 600, fontSize: 14 }}>Google Drive</div>
          <div style={{ fontSize: 12.5, color: "var(--text-muted)", marginTop: 2 }}>
            Connect your Drive to push photos and videos straight into Vanday. We only see files you explicitly share.
          </div>
        </div>
        {loading ? (
          <span style={{ fontSize: 12, color: "var(--text-faint)" }}>Checking…</span>
        ) : conn ? (
          <>
            <span style={{
              padding: "4px 10px", borderRadius: 999,
              fontSize: 11.5, fontWeight: 600,
              background: "oklch(0.93 0.07 155)", color: "oklch(0.32 0.13 155)",
            }}>
              {conn.external_email ? `Connected · ${conn.external_email}` : "Connected"}
            </span>
            <button className="btn" onClick={onDisconnect}>Disconnect</button>
          </>
        ) : (
          <button className="btn primary" onClick={onConnect}>
            <IcExternal size={13} /> Connect
          </button>
        )}
      </div>
      {error && (
        <div style={{
          padding: "10px 20px", fontSize: 12.5,
          background: "oklch(0.96 0.04 24)", color: "oklch(0.42 0.16 24)",
          borderTop: "1px solid oklch(0.85 0.08 24)",
        }}>{error}</div>
      )}
      {conn && <DriveImportTester authHeaders={authHeaders} />}
    </div>
  );
}

// ============================================================
// DriveImportTester — temporary scaffolding so we can verify the import
// pipeline end-to-end before the in-Drive "Add to Vanday" add-on lands.
//
// Paste Drive file IDs (one per line — they're the slug in
// drive.google.com/file/d/<this>/view URLs) and hit Import. Each goes
// through getFileMetadata → downloadFile → insertAsset → processAI on
// the server. Results show inline with errors.
// ============================================================
function DriveImportTester({ authHeaders }) {
  const [ids, setIds] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const [result, setResult] = React.useState(null);

  // Accept Drive URLs OR raw IDs in the textarea. Extract the underlying
  // file ID for known URL patterns. Folder URLs are detected so we can show
  // a clear error instead of letting Drive 404 with a wall of JSON.
  const parseToken = (raw) => {
    const s = raw.trim();
    if (!s) return { kind: "empty" };
    let m;
    if ((m = s.match(/\/file\/d\/([a-zA-Z0-9_-]+)/))) return { kind: "file", id: m[1] };
    if ((m = s.match(/[?&]id=([a-zA-Z0-9_-]+)/)))      return { kind: "file", id: m[1] };
    if ((m = s.match(/\/folders\/([a-zA-Z0-9_-]+)/)))  return { kind: "folder", id: m[1] };
    // Bare ID? Drive IDs are alnum + - + _, ~20-60 chars long
    if (/^[a-zA-Z0-9_-]{20,60}$/.test(s))              return { kind: "file", id: s };
    return { kind: "unknown", raw: s };
  };

  const onImport = async () => {
    const tokens = ids.split(/[\s,]+/).filter(Boolean);
    const file_ids = [];
    const folders = [];
    const unknowns = [];
    for (const t of tokens) {
      const p = parseToken(t);
      if (p.kind === "file")   file_ids.push(p.id);
      else if (p.kind === "folder") folders.push(p.id);
      else if (p.kind === "unknown") unknowns.push(p.raw);
    }

    if (folders.length > 0) {
      setResult({ ok: false, body: {
        error: "folder_urls_not_supported_yet",
        note: "Folder import will come with the in-Drive \"Add to Vanday\" button (Phase 3). For now, open each file in Drive (right-click → Open in new tab) and paste its individual URL.",
        folders,
      }});
      return;
    }
    if (file_ids.length === 0) {
      setResult({ ok: false, body: {
        error: "no_file_ids_found",
        note: "Couldn't pull a Drive file ID out of your input. Paste a Drive file URL (.../file/d/<id>/view) or just the <id>.",
        unrecognized: unknowns,
      }});
      return;
    }

    setBusy(true); setResult(null);
    try {
      const r = await fetch("/api/integrations/google/import", {
        method: "POST",
        headers: { "Content-Type": "application/json", ...(await authHeaders()) },
        body: JSON.stringify({ file_ids, folder: "raw" }),
      });
      const body = await r.json();
      setResult({ ok: r.ok, body });
    } catch (err) {
      setResult({ ok: false, body: { error: String(err?.message || err) } });
    } finally {
      setBusy(false);
    }
  };

  return (
    <div style={{
      padding: "16px 20px",
      borderTop: "1px solid var(--border)",
      background: "var(--surface-sunken)",
    }}>
      <div style={{ fontSize: 11, fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.06em", color: "var(--text-faint)", marginBottom: 8 }}>
        Test import (scaffolding — the real "Add to Vanday" button lives inside Drive itself)
      </div>
      <p style={{ fontSize: 12.5, color: "var(--text-muted)", margin: "0 0 10px", lineHeight: 1.5 }}>
        Paste one or more Drive file IDs (the bit between <span style={{ fontFamily: "var(--font-mono)" }}>/file/d/</span> and <span style={{ fontFamily: "var(--font-mono)" }}>/view</span> in a Drive URL). Up to 25 per request, 100 MB per file. Images go through auto-tagging.
      </p>
      <textarea
        value={ids}
        onChange={(e) => setIds(e.target.value)}
        rows={3}
        placeholder="e.g. 1AbC2dEfGhIjKlMnOpQrStUvWxYz_123"
        style={{
          width: "100%", padding: 10, fontSize: 12, lineHeight: 1.5,
          fontFamily: "var(--font-mono)",
          background: "var(--surface)", color: "var(--text)",
          border: "1px solid var(--border)", borderRadius: 8,
          resize: "vertical", outline: "none",
        }}
      />
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 10, gap: 12 }}>
        <span style={{ fontSize: 11.5, color: "var(--text-faint)" }}>
          {ids.trim().split(/[\s,]+/).filter(Boolean).length} file ID{ids.trim().split(/[\s,]+/).filter(Boolean).length === 1 ? "" : "s"}
        </span>
        <button
          type="button"
          className="btn accent"
          onClick={onImport}
          disabled={busy || !ids.trim()}
          style={busy || !ids.trim() ? { opacity: 0.5, cursor: "not-allowed" } : undefined}
        >
          {busy ? "Importing…" : "Import"}
        </button>
      </div>
      {result && (
        <div style={{
          marginTop: 12, padding: 10, borderRadius: 8,
          background: "var(--surface)", border: "1px solid var(--border)",
          fontSize: 12, fontFamily: "var(--font-mono)",
          maxHeight: 240, overflowY: "auto",
        }}>
          <pre style={{ margin: 0, whiteSpace: "pre-wrap", lineHeight: 1.5 }}>
            {JSON.stringify(result.body, null, 2)}
          </pre>
        </div>
      )}
    </div>
  );
}

// ============================================================
// Unified integration registry — single source of truth for everything that
// can be connected from Vanday. Drive uses our own OAuth flow; social
// platforms route through Upload-Post's hosted connect page. New items just
// get an entry here and the IntegrationsHub renders them automatically.
//
// Fields:
//   id          — provider key (matches /api/integrations.provider for
//                 OAuth integrations; matches Upload-Post's platform id for
//                 social).
//   name        — display label.
//   category    — section header in the hub.
//   blurb       — short description shown under the name.
//   mark        — 2-char monogram for the colored tile.
//   color       — tile background (oklch).
//   status      — "live" (can connect today) | "coming_soon" (waitlist).
//   connectFlow — "oauth-google" | "upload-post" | null.
//   requiresPlan— null = available to everyone; "starter" / "pro" etc. =
//                 locked behind that plan, surfaces as an upgrade upsell.
// ============================================================
const INTEGRATIONS = [
  // --- Storage & Import --------------------------------------------------
  {
    id: "google_drive", name: "Google Drive", category: "Storage & Import",
    mark: "DR", color: "oklch(0.55 0.16 60)",
    blurb: "Push photos and videos from Drive into Vanday. We only see files you explicitly share.",
    status: "live", connectFlow: "oauth-google",
  },
  {
    id: "dropbox", name: "Dropbox", category: "Storage & Import",
    mark: "DB", color: "oklch(0.55 0.14 250)",
    blurb: "Mirror a Dropbox folder. Auto-tagged + versioned on arrival.",
    status: "coming_soon",
  },

  // --- Publishing (Upload-Post backed) -----------------------------------
  {
    id: "instagram", name: "Instagram", category: "Publishing",
    mark: "IG", color: "oklch(0.62 0.18 18)",
    blurb: "Post images + reels to your business or creator account.",
    status: "live", connectFlow: "upload-post",
  },
  {
    id: "facebook", name: "Facebook", category: "Publishing",
    mark: "FB", color: "oklch(0.48 0.13 250)",
    blurb: "Post to a Facebook Page you manage (not personal profile).",
    status: "live", connectFlow: "upload-post",
  },
  {
    id: "linkedin", name: "LinkedIn", category: "Publishing",
    mark: "in", color: "oklch(0.42 0.13 240)",
    blurb: "Post to your personal LinkedIn or a Company Page.",
    status: "live", connectFlow: "upload-post",
  },
  {
    id: "pinterest", name: "Pinterest", category: "Publishing",
    mark: "P",  color: "oklch(0.55 0.18 18)",
    blurb: "Pin to a board on your connected Pinterest account.",
    status: "live", connectFlow: "upload-post",
  },
  {
    id: "x", name: "X", category: "Publishing",
    mark: "X", color: "oklch(0.2 0.005 80)",
    blurb: "Post tweets to your X account.",
    status: "live", connectFlow: "upload-post",
  },
  {
    id: "tiktok", name: "TikTok", category: "Publishing",
    mark: "TT", color: "oklch(0.22 0.04 200)",
    blurb: "Cross-post short-form video to TikTok.",
    status: "coming_soon",
  },

  // --- Design tools ------------------------------------------------------
  {
    id: "canva", name: "Canva", category: "Design tools",
    mark: "CV", color: "oklch(0.52 0.18 250)",
    blurb: "Browse your Vanday library inside Canva; save designs back.",
    status: "coming_soon",
  },
  {
    id: "figma", name: "Figma", category: "Design tools",
    mark: "FG", color: "oklch(0.3 0.005 80)",
    blurb: "Drop Vanday assets straight onto your Figma canvas.",
    status: "coming_soon",
  },
  {
    id: "adobe_cc", name: "Adobe Creative Cloud", category: "Design tools",
    mark: "Aa", color: "oklch(0.45 0.2 28)",
    blurb: "Sync Vanday assets to CC Libraries — show up in PS, AI, InDesign.",
    status: "coming_soon",
  },
];
const INTEGRATION_GROUPS = ["Storage & Import", "Publishing", "Design tools"];

// ============================================================
// IntegrationsHub — the unified Connections + Integrations page.
//
// Renders every integration in INTEGRATIONS grouped by category. Live ones
// fetch their connection state from one of two endpoints:
//   - oauth-google     → /api/integrations           (Drive)
//   - upload-post      → /api/social/connections     (5 social platforms)
//
// Each card resolves to one of these states:
//   "connected"     → green pill + Disconnect / Manage button
//   "available"     → terracotta Connect button
//   "coming_soon"   → soft "Coming soon" badge + "Notify me" link to the
//                     existing feedback form at the bottom of the page
//   "locked"        → "Upgrade" CTA (when requiresPlan is set and the
//                     user's plan doesn't satisfy it — currently unused
//                     but wired so flipping any integration to a paid plan
//                     just works)
// ============================================================
function IntegrationsHub() {
  // Live state from both auth backends.
  const [oauthConns, setOauthConns] = React.useState(null);   // array from /api/integrations
  const [socialConns, setSocialConns] = React.useState(null); // map from /api/social/connections.platforms
  const [error, setError] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  // Collapse state for the Connected / Not connected groups. Both open by
  // default; the not-connected list is collapsible since the user may never
  // want to wire those up.
  const [connectedOpen, setConnectedOpen] = React.useState(true);
  const [notConnectedOpen, setNotConnectedOpen] = React.useState(true);

  // Plan info → used by `locked` detection.
  const me = (typeof window.useVandayMe === "function") ? window.useVandayMe() : null;
  const planName = me?.plan?.name || "free_beta";
  const planAllows = (required) => {
    if (!required) return true;
    if (planName === "pro" || planName === "starter" || planName === "growth") return true;
    return false;
  };

  const authHeaders = React.useCallback(async () => {
    try {
      const t = window.Clerk?.session ? await window.Clerk.session.getToken() : null;
      return t ? { Authorization: `Bearer ${t}` } : {};
    } catch { return {}; }
  }, []);

  const load = React.useCallback(async () => {
    setError(null);
    try {
      const [r1, r2] = await Promise.all([
        fetch("/api/integrations",        { headers: await authHeaders() }),
        fetch("/api/social/connections",  { headers: await authHeaders() }),
      ]);
      const j1 = await r1.json();
      const j2 = await r2.json();
      setOauthConns(r1.ok ? (j1.connections || []) : []);
      setSocialConns(r2.ok ? (j2.platforms || {}) : {});
    } catch { setError("Couldn't load integration status."); }
  }, [authHeaders]);
  React.useEffect(() => { load(); }, [load]);
  // If user returns from an Upload-Post connect tab, refresh.
  React.useEffect(() => {
    const onFocus = () => load();
    window.addEventListener("focus", onFocus);
    return () => window.removeEventListener("focus", onFocus);
  }, [load]);

  // Compute resolved state for an integration entry.
  const resolveStatus = (it) => {
    if (it.status === "coming_soon") return "coming_soon";
    if (!planAllows(it.requiresPlan)) return "locked";
    if (it.connectFlow === "oauth-google") {
      const c = (oauthConns || []).find((x) => x.provider === it.id);
      if (c) return { state: "connected", detail: c.external_email };
    }
    if (it.connectFlow === "upload-post") {
      const s = socialConns?.[it.id];
      if (s?.status === "connected") return { state: "connected", detail: s.detail };
    }
    return "available";
  };

  // --- Connect / disconnect handlers ---------------------------------------
  const openGoogleConnect = () => {
    const ret = encodeURIComponent("/#settings/integrations");
    window.location.href = `/api/integrations/google/connect?return=${ret}`;
  };
  const disconnectGoogle = async () => {
    if (!window.confirm("Disconnect Google Drive? Imported assets stay; future imports stop.")) return;
    try {
      await fetch("/api/integrations/google/disconnect", { method: "POST", headers: await authHeaders() });
      load();
    } catch { setError("Disconnect failed — try refreshing."); }
  };
  const openUploadPostConnect = async (platformId) => {
    setBusy(true);
    try {
      const redirectUrl = `${window.location.origin}/?back_from=connect#settings/integrations`;
      const r = await fetch("/api/social/connect-link", {
        method: "POST",
        headers: { "Content-Type": "application/json", ...(await authHeaders()) },
        body: JSON.stringify({ platforms: [platformId], redirectUrl }),
      });
      const body = await r.json();
      if (!r.ok || !body.accessUrl) {
        setError("Couldn't generate connect link: " + (body.error || `HTTP ${r.status}`));
        return;
      }
      window.open(body.accessUrl, "_blank", "noopener,noreferrer");
    } catch { setError("Couldn't reach Upload-Post — try again."); }
    finally { setBusy(false); }
  };

  // --- Render helpers ------------------------------------------------------
  const Card = ({ it }) => {
    const status = resolveStatus(it);
    const state  = typeof status === "string" ? status : status.state;
    const detail = typeof status === "object" ? status.detail : null;
    const baseRow = {
      display: "flex", alignItems: "center", gap: 14, padding: "16px 20px",
      background: state === "coming_soon" ? "var(--surface-soft)" : "var(--surface)",
      opacity: state === "coming_soon" ? 0.78 : 1,
    };
    const renderAction = () => {
      if (state === "coming_soon") {
        return <ComingSoonRequest it={it} />;
      }
      if (state === "locked") {
        return (
          <button className="btn primary" disabled style={{ opacity: 0.85 }}>
            Upgrade to {it.requiresPlan} →
          </button>
        );
      }
      if (state === "connected") {
        if (it.connectFlow === "oauth-google") {
          return <button className="btn" onClick={disconnectGoogle}>Disconnect</button>;
        }
        if (it.connectFlow === "upload-post") {
          return (
            <button className="btn" onClick={() => openUploadPostConnect(it.id)} disabled={busy} title="Manage in Upload-Post (opens new tab)">
              <IcExternal size={13} /> Manage
            </button>
          );
        }
      }
      // available
      if (it.connectFlow === "oauth-google") {
        return <button className="btn primary" onClick={openGoogleConnect}>Connect</button>;
      }
      if (it.connectFlow === "upload-post") {
        return (
          <button className="btn primary" onClick={() => openUploadPostConnect(it.id)} disabled={busy}>
            <IcExternal size={13} /> Connect
          </button>
        );
      }
      return null;
    };

    const StatusPill = () => {
      if (state === "connected") return (
        <span style={{ padding: "3px 9px", borderRadius: 999, fontSize: 11.5, fontWeight: 600,
          background: "oklch(0.93 0.07 155)", color: "oklch(0.32 0.13 155)" }}>
          {detail ? `Connected · ${detail}` : "Connected"}
        </span>
      );
      if (state === "coming_soon") return (
        <span style={{ padding: "3px 9px", borderRadius: 999, fontSize: 11.5, fontWeight: 500,
          background: "var(--surface-sunken)", color: "var(--text-muted)" }}>Coming soon</span>
      );
      if (state === "locked") return (
        <span style={{ padding: "3px 9px", borderRadius: 999, fontSize: 11.5, fontWeight: 600,
          background: "var(--accent-soft)", color: "var(--accent)" }}>{it.requiresPlan} plan</span>
      );
      return null; // "available" — no badge needed
    };

    return (
      <div className="section-card" style={{ padding: 0, overflow: "hidden" }}>
        <div style={baseRow}>
          <div aria-hidden style={{
            width: 36, height: 36, borderRadius: 8,
            background: it.color, color: "white",
            display: "grid", placeItems: "center",
            fontWeight: 700, fontSize: 12, flexShrink: 0,
            fontFamily: "var(--font-mono)",
          }}>{it.mark}</div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
              <div style={{ fontWeight: 600, fontSize: 14 }}>{it.name}</div>
              <StatusPill />
            </div>
            <div style={{ fontSize: 12, color: "var(--text-muted)", marginTop: 4, lineHeight: 1.45 }}>
              {it.blurb}
            </div>
          </div>
          {renderAction()}
        </div>
      </div>
    );
  };

  // Group by connection status rather than category. An integration counts
  // as connected only when resolveStatus says so; everything else (available,
  // coming soon, locked) lands in "Not connected".
  const isConnectedIt = (it) => {
    const st = resolveStatus(it);
    return (typeof st === "string" ? st : st.state) === "connected";
  };
  const connectedItems = INTEGRATIONS.filter(isConnectedIt);
  const notConnectedItems = INTEGRATIONS.filter((it) => !isConnectedIt(it));

  const renderGroup = (title, items, open, onToggle, emptyText) => (
    <div style={{ marginTop: 24 }}>
      <button
        type="button"
        onClick={onToggle}
        aria-expanded={open}
        style={{
          display: "flex", alignItems: "center", gap: 7, width: "100%",
          padding: 0, marginBottom: 10, background: "transparent", border: 0,
          cursor: "pointer",
          fontSize: 11, fontWeight: 600, textTransform: "uppercase",
          letterSpacing: "0.06em", color: "var(--text-faint)",
        }}
      >
        <IcChevD size={14} style={{ transform: open ? "none" : "rotate(-90deg)", transition: "transform .15s" }} />
        <span>{title}</span>
        <span style={{ marginLeft: 2, fontWeight: 500 }}>· {items.length}</span>
      </button>
      {open && (
        items.length === 0 ? (
          <div style={{ fontSize: 13, color: "var(--text-faint)", padding: "2px 0 4px 21px" }}>{emptyText}</div>
        ) : (
          <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
            {items.map((it) => <Card key={it.id} it={it} />)}
          </div>
        )
      )}
    </div>
  );

  return (
    <>
      <h1 className="settings-h1">Integrations</h1>
      <p className="settings-sub" style={{ marginBottom: 24 }}>
        Connect Vanday to the tools your team already uses for storage,
        publishing, and design. New integrations land in waves —
        Google Drive and the publishing channels are live now.
      </p>

      {error && (
        <div style={{
          marginBottom: 16, padding: 12,
          background: "oklch(0.96 0.04 24)", border: "1px solid oklch(0.85 0.08 24)",
          borderRadius: 8, fontSize: 13, color: "oklch(0.42 0.16 24)",
        }}>{error}</div>
      )}

      {renderGroup(
        "Connected", connectedItems, connectedOpen,
        () => setConnectedOpen((o) => !o),
        "Nothing connected yet — connect a tool from below.",
      )}
      {renderGroup(
        "Not connected", notConnectedItems, notConnectedOpen,
        () => setNotConnectedOpen((o) => !o),
        "Everything's connected. Nice.",
      )}

      <div style={{
        fontSize: 11, fontWeight: 500, textTransform: "uppercase",
        letterSpacing: "0.06em", color: "var(--text-faint)",
        margin: "32px 0 10px",
      }}>Don't see what you need?</div>
      <IntegrationsFeedbackForm />
    </>
  );
}

// ============================================================
// ComingSoonRequest — one-click "I want this" on a coming-soon integration
// card. Records a feature request (kind: "integration") tagged with the
// specific integration name and confirms inline. These land in the founder's
// Admin dashboard → Requests tab, same store as the free-text feedback box.
// ============================================================
function ComingSoonRequest({ it }) {
  const [state, setState] = React.useState("idle"); // idle | sending | done | error

  const request = async () => {
    if (state === "sending" || state === "done") return;
    setState("sending");
    try {
      const r = await fetch("/api/feature-request", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          ...(window.Clerk?.session ? { Authorization: `Bearer ${await window.Clerk.session.getToken()}` } : {}),
        },
        body: JSON.stringify({ kind: "integration", body: `Wants integration: ${it.name}` }),
      });
      if (!r.ok) throw new Error("server " + r.status);
      setState("done");
    } catch {
      setState("error");
    }
  };

  if (state === "done") {
    return (
      <span style={{ fontSize: 12, color: "oklch(0.5 0.13 155)", fontWeight: 500,
        display: "inline-flex", alignItems: "center", gap: 5 }}>
        <IcCheck size={13} /> Thanks — we'll keep you posted
      </span>
    );
  }
  return (
    <button
      type="button"
      onClick={request}
      disabled={state === "sending"}
      title={`Let us know you'd use the ${it.name} integration`}
      style={{
        fontSize: 12, color: "var(--accent)", fontWeight: 500,
        background: "transparent", border: 0, padding: 0,
        cursor: state === "sending" ? "wait" : "pointer",
      }}
    >
      {state === "sending" ? "Sending…" : state === "error" ? "Hmm — try again →" : "Tell us you want this →"}
    </button>
  );
}

// ============================================================
// IntegrationsFeedbackForm — the "tell us what's next" textarea that used
// to be inside IntegrationsComingSoon. Extracted so the unified hub can
// render it as a footer section.
// ============================================================
function IntegrationsFeedbackForm() {
  const [text, setText] = React.useState("");
  const [submitted, setSubmitted] = React.useState(false);
  const [submitting, setSubmitting] = React.useState(false);
  const [error, setError] = React.useState(null);

  const submit = async () => {
    const body = text.trim();
    if (!body) return;
    setSubmitting(true); setError(null);
    try {
      const r = await fetch("/api/feature-request", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          ...(window.Clerk?.session ? { Authorization: `Bearer ${await window.Clerk.session.getToken()}` } : {}),
        },
        body: JSON.stringify({ kind: "integration", body }),
      });
      if (!r.ok) throw new Error("server " + r.status);
      setSubmitted(true);
      setText("");
    } catch (err) {
      setError("Couldn't send your request. Try again in a moment.");
    } finally {
      setSubmitting(false);
    }
  };

  const POPULAR = [
    "Instagram", "TikTok", "Pinterest", "YouTube Shorts", "LinkedIn",
    "Facebook", "X (Twitter)", "Threads", "Shopify", "Webflow",
    "Notion", "Slack", "Dropbox", "Google Drive", "Adobe Creative Cloud",
  ];

  return (
    <div className="section-card" style={{ padding: 24 }}>
      <label htmlFor="fr-text" style={{ display: "block", fontWeight: 600, fontSize: 14, marginBottom: 8 }}>
        Which integrations would you use?
      </label>
      <textarea
        id="fr-text"
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="e.g. Notion docs to assets; Slack alerts on publish; Webflow image swap…"
        rows={4}
        maxLength={1000}
        style={{
          width: "100%", padding: 12, fontSize: 13.5, lineHeight: 1.5,
          background: "var(--surface)", color: "var(--text)",
          border: "1px solid var(--border)", borderRadius: 8,
          resize: "vertical", outline: "none", fontFamily: "inherit",
        }}
      />
      <div style={{ display: "flex", flexWrap: "wrap", gap: 6, marginTop: 10 }}>
        {POPULAR.map((p) => (
          <button
            key={p}
            type="button"
            onClick={() => setText((t) => (t ? t + ", " + p : p))}
            style={{
              padding: "4px 10px", fontSize: 12,
              background: "var(--surface)", color: "var(--text-muted)",
              border: "1px solid var(--border)", borderRadius: 999,
              cursor: "pointer",
            }}
          >+ {p}</button>
        ))}
      </div>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 16, gap: 12 }}>
        <div style={{ fontSize: 12.5, color: submitted ? "oklch(0.5 0.13 155)" : "var(--text-faint)" }}>
          {submitted ? "✓ Thanks — we recorded your request." : (error || `${text.length} / 1000`)}
        </div>
        <button
          type="button"
          className="btn primary"
          onClick={submit}
          disabled={submitting || !text.trim()}
          style={submitting || !text.trim() ? { opacity: 0.5, cursor: "not-allowed" } : undefined}
        >
          {submitting ? "Sending…" : "Send request"}
        </button>
      </div>
    </div>
  );
}

// Backwards-compat alias — the SettingsPage section switch still references
// IntegrationsComingSoon in one place. Point it at the new hub so we don't
// have to touch settings.jsx; the old name will fade in a follow-up cleanup.
const IntegrationsComingSoon = IntegrationsHub;

Object.assign(window, {
  IntegrationMark,
  IntegrationsSection,
  IntegrationsHub,
  IntegrationsComingSoon,
  ApiSection,
  SendToMenu,
  SendToModal,
});
