// App root — router + state

const ClearCastContext = React.createContext(null);

function useClearCast() {
  return React.useContext(ClearCastContext);
}

function PublicBioPage({ handle }) {
  const hub = useClearCast();
  const page = hub.data.linkBioPage || {};
  const links = (hub.data.linkBio || [])
    .filter((link) => link.active !== false)
    .sort((a, b) => (a.order || 0) - (b.order || 0));
  const theme = page.theme || "warm";
  const bg = theme === "dark" ? "linear-gradient(180deg, #1a1a1a, #0a0a0a)" : theme === "rose" ? "linear-gradient(180deg, #ffd8d0, #d88880)" : theme === "forest" ? "linear-gradient(180deg, #284038, #142420)" : "linear-gradient(180deg, #f4e8d0, #d8b890)";
  const light = theme === "warm" || theme === "rose";
  const click = async (link) => {
    await hub.actions.clickBioLink(link.id);
    if (link.url?.startsWith("http")) window.open(link.url, "_blank");
  };

  if (handle && page.handle && handle !== page.handle) {
    return <div className="public-bio" style={{ background: bg }}><div className="empty-note">This bio page was not found.</div></div>;
  }

  return (
    <div className="public-bio" style={{ background: bg, color: light ? "#2a1a0a" : "white" }}>
      <div className="public-bio-card">
        <div className="brand-mark brand-mark--logo" style={{ width: 72, height: 72, margin: "0 auto 14px" }}><BrandMark /></div>
        <h1>@{page.handle || "emma"}</h1>
        <p>{page.bio}</p>
        <div className="public-bio-links">
          {links.map((link) => (
            <button key={link.id} onClick={() => click(link)} className="public-bio-link">
              <span>{link.icon || "✦"}</span>
              <b>{link.title}</b>
            </button>
          ))}
        </div>
      </div>
    </div>
  );
}

function OnboardingPage() {
  const hub = useClearCast();
  const data = hub.data || {};
  const setPage = hub.setPage;
  const businessName = data.workspace?.name || "your business";
  const connected = (data.integrations || []).filter((integration) => integration.connected).length;
  const postCount = (data.posts || []).length;
  const scheduled = (data.posts || []).filter((post) => ["scheduled", "published"].includes(post.status)).length;
  const steps = [
    { key: "connect", title: "Connect your accounts", desc: "Link the social channels you post to — you can add more anytime.", done: connected > 0, cta: connected > 0 ? "Manage channels" : "Connect channels", go: "integrations" },
    { key: "create", title: "Create your first post", desc: "Write it yourself or generate a caption, image, or video with AI.", done: postCount > 0, cta: "Create a post", go: "create" },
    { key: "schedule", title: "Schedule it", desc: "Pick the best time and add it to your calendar.", done: scheduled > 0, cta: "Open calendar", go: "plan" }
  ];
  const doneCount = steps.filter((step) => step.done).length;
  const skip = () => {
    try {
      const map = JSON.parse(window.localStorage.getItem("clearcast-onboard-skip") || "{}");
      map[data.activeWorkspaceId] = true;
      window.localStorage.setItem("clearcast-onboard-skip", JSON.stringify(map));
    } catch (error) { /* ignore */ }
    setPage("create");
  };
  return (
    <div className="page">
      <div className="onboarding-wrap">
        <div className="onboarding-head">
          <div className="brand-mark brand-mark--logo" style={{ width: 44, height: 44 }}><BrandMark /></div>
          <div>
            <h1 style={{ fontSize: 22, fontWeight: 700, margin: 0 }}>Welcome to {businessName}</h1>
            <p className="muted" style={{ margin: "4px 0 0", fontSize: 13 }}>Three quick steps to your first scheduled post.</p>
          </div>
        </div>
        <div className="onboarding-progress">
          <div className="onboarding-progress-bar"><span style={{ width: `${(doneCount / steps.length) * 100}%` }} /></div>
          <span className="muted" style={{ fontSize: 12 }}>{doneCount} of {steps.length} done</span>
        </div>
        <div className="onboarding-steps">
          {steps.map((step, index) => (
            <div key={step.key} className={`onboarding-step ${step.done ? "done" : ""}`}>
              <div className="onboarding-step-num">{step.done ? <Icon.Check width="16" height="16" /> : index + 1}</div>
              <div className="onboarding-step-body">
                <b>{step.title}</b>
                <small>{step.desc}</small>
              </div>
              <button className={`btn btn-sm ${step.done ? "" : "btn-primary"}`} onClick={() => setPage(step.go)}>{step.cta}</button>
            </div>
          ))}
        </div>
        <div className="onboarding-foot">
          <button className="btn" onClick={skip}>Skip for now</button>
          {doneCount === steps.length && <button className="btn btn-primary" onClick={() => setPage("create")}>You're all set — start posting</button>}
        </div>
      </div>
    </div>
  );
}

function LoginPage() {
  const hub = useClearCast();
  const signupsOpen = !!(hub.data && hub.data.signupsOpen);
  const [mode, setMode] = React.useState("login");
  const [email, setEmail] = React.useState("");
  const [password, setPassword] = React.useState("");
  const [name, setName] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState("");
  const [mfaStep, setMfaStep] = React.useState(false);
  const [code, setCode] = React.useState("");
  const [ssoMode, setSsoMode] = React.useState(false);
  const [ssoEmail, setSsoEmail] = React.useState("");
  const startSso = () => {
    const v = ssoEmail.trim().toLowerCase();
    const domain = v.includes("@") ? v.split("@")[1] : v;
    if (domain) window.location.href = "/api/auth/sso?domain=" + encodeURIComponent(domain);
  };

  const submit = async (e) => {
    if (e) e.preventDefault();
    setError("");
    setBusy(true);
    try {
      if (mode === "login") {
        const res = await hub.actions.login(email.trim(), password);
        if (res && res.mfaRequired) { setMfaStep(true); return; }  // ask for the 6-digit code
      } else {
        await hub.actions.signup({ email: email.trim(), password, name: name.trim() });
      }
    } catch (err) {
      setError(err.message || "Something went wrong");
    } finally { setBusy(false); }
  };

  const submitCode = async (e) => {
    if (e) e.preventDefault();
    setError(""); setBusy(true);
    try {
      await hub.actions.mfaLogin(email.trim(), password, code.trim());
    } catch (err) {
      setError(err.message || "That code wasn't valid");
    } finally { setBusy(false); }
  };

  if (mfaStep) {
    return (
      <div className="review-shell">
        <form className="review-card" style={{ maxWidth: 420 }} onSubmit={submitCode}>
          <div className="review-head">
            <div className="brand-mark brand-mark--logo" style={{ width: 40, height: 40 }}><BrandMark /></div>
            <div>
              <div style={{ fontWeight: 700, fontSize: 17 }}>Two-factor authentication</div>
              <div className="muted" style={{ fontSize: 12 }}>Enter the 6-digit code from your authenticator app</div>
            </div>
          </div>
          <input className="input" inputMode="numeric" autoComplete="one-time-code" placeholder="123456" value={code} onChange={(e) => setCode(e.target.value.replace(/\D/g, "").slice(0, 6))} style={{ marginBottom: 10, letterSpacing: 4, textAlign: "center", fontSize: 18 }} autoFocus />
          {error && <div style={{ color: "var(--danger)", fontSize: 12.5, marginBottom: 10 }}>{error}</div>}
          <button type="submit" className="btn btn-primary" disabled={busy || code.length < 6} style={{ width: "100%", justifyContent: "center" }}>{busy ? "Verifying…" : "Verify & sign in"}</button>
          <div style={{ textAlign: "center", marginTop: 14, fontSize: 12.5 }} className="muted">
            <button type="button" onClick={() => { setMfaStep(false); setCode(""); setError(""); }} style={{ color: "var(--accent-2)", fontWeight: 600 }}>Back</button>
          </div>
        </form>
      </div>
    );
  }

  return (
    <div className="review-shell">
      <form className="review-card" style={{ maxWidth: 420 }} onSubmit={submit}>
        <div className="review-head">
          <div className="brand-mark brand-mark--logo" style={{ width: 40, height: 40 }}><BrandMark /></div>
          <div>
            <div style={{ fontWeight: 700, fontSize: 17 }}>ClearCast</div>
            <div className="muted" style={{ fontSize: 12 }}>{mode === "login" ? "Sign in to your workspace" : "Create your account"}</div>
          </div>
        </div>
        <button type="button" className="lp-google-btn" onClick={() => { window.location.href = "/api/auth/google"; }}>
          <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.76h3.56c2.08-1.92 3.28-4.74 3.28-8.09z"/><path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.56-2.76c-.98.66-2.23 1.06-3.72 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84A11 11 0 0 0 12 23z"/><path fill="#FBBC05" d="M5.84 14.11c-.22-.66-.35-1.36-.35-2.11s.13-1.45.35-2.11V7.05H2.18A11 11 0 0 0 1 12c0 1.77.42 3.45 1.18 4.95l3.66-2.84z"/><path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.05l3.66 2.84C6.71 7.29 9.14 5.38 12 5.38z"/></svg>
          Continue with Google
        </button>
        <div className="lp-or"><span>or</span></div>
        {mode === "signup" && <input className="input" placeholder="Your name" value={name} onChange={(e) => setName(e.target.value)} style={{ marginBottom: 10 }} />}
        <input className="input" type="email" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} style={{ marginBottom: 10 }} autoFocus />
        <input className="input" type="password" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} style={{ marginBottom: 10 }} />
        {error && <div style={{ color: "var(--danger)", fontSize: 12.5, marginBottom: 10 }}>{error}</div>}
        <button type="submit" className="btn btn-primary" disabled={busy || !email || !password} style={{ width: "100%", justifyContent: "center" }}>
          {busy ? "Please wait…" : mode === "login" ? "Sign in" : "Create account"}
        </button>
        <div style={{ textAlign: "center", marginTop: 14, fontSize: 12.5 }} className="muted">
          {mode === "signup"
            ? <>Already have an account? <button type="button" onClick={() => { setMode("login"); setError(""); }} style={{ color: "var(--accent-2)", fontWeight: 600 }}>Sign in</button></>
            : signupsOpen
              ? <>New here? <button type="button" onClick={() => { setMode("signup"); setError(""); }} style={{ color: "var(--accent-2)", fontWeight: 600 }}>Create an account</button></>
              : <>ClearCast is invite-only during early access.</>}
        </div>
        {mode === "login" && (
          <div style={{ marginTop: 12, paddingTop: 12, borderTop: "1px solid var(--border)" }}>
            {!ssoMode ? (
              <div style={{ textAlign: "center", fontSize: 12.5 }} className="muted">
                <button type="button" onClick={() => setSsoMode(true)} style={{ color: "var(--accent-2)", fontWeight: 600 }}>Sign in with SSO</button>
              </div>
            ) : (
              <div style={{ display: "flex", gap: 8 }}>
                <input className="input" type="email" placeholder="you@company.com" value={ssoEmail} onChange={(e) => setSsoEmail(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); startSso(); } }} style={{ flex: 1 }} />
                <button type="button" className="btn" onClick={startSso} disabled={!ssoEmail.trim()}>Continue</button>
              </div>
            )}
          </div>
        )}
      </form>
    </div>
  );
}

// Lands here after Google/OAuth round-trip: Supabase returns the session in the
// URL fragment (#access_token=...). We capture it synchronously (before any
// router effect can rewrite the hash), verify it server-side, then enter the app.
function AuthCallback() {
  const hub = useClearCast();
  const [parsed] = React.useState(() => {
    const frag = new URLSearchParams((window.location.hash || "").replace(/^#/, ""));
    return { token: frag.get("access_token"), error: frag.get("error_description") || frag.get("error") };
  });
  const [status, setStatus] = React.useState("working");
  const [message, setMessage] = React.useState("");

  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      if (parsed.error) { setStatus("error"); setMessage(decodeURIComponent(parsed.error)); return; }
      if (!parsed.token) { setStatus("error"); setMessage("No sign-in details were returned. Please try again."); return; }
      try {
        await hub.actions.completeGoogleLogin(parsed.token);
        if (!cancelled) window.location.replace("/");
      } catch (err) {
        if (!cancelled) { setStatus("error"); setMessage(err.message || "We couldn't finish your Google sign-in."); }
      }
    })();
    return () => { cancelled = true; };
  }, []);

  return (
    <div className="review-shell">
      <div className="review-card" style={{ maxWidth: 380, textAlign: "center" }}>
        <div className="brand-mark brand-mark--logo" style={{ width: 44, height: 44, margin: "0 auto 14px" }}><BrandMark /></div>
        {status === "working" ? (
          <>
            <div style={{ fontWeight: 700, fontSize: 16 }}>Signing you in…</div>
            <div className="muted" style={{ fontSize: 13, marginTop: 6 }}>Completing your Google sign-in.</div>
          </>
        ) : (
          <>
            <div style={{ fontWeight: 700, fontSize: 16 }}>Sign-in didn't finish</div>
            <div className="muted" style={{ fontSize: 13, marginTop: 6 }}>{message}</div>
            <button className="btn btn-primary" style={{ marginTop: 16 }} onClick={() => window.location.replace("/")}>Back to sign in</button>
          </>
        )}
      </div>
    </div>
  );
}

function ReviewPage({ token }) {
  const [state, setState] = React.useState({ loading: true });
  const [reviewer, setReviewer] = React.useState("");
  const [note, setNote] = React.useState("");
  const [busy, setBusy] = React.useState("");
  const [done, setDone] = React.useState("");

  const load = React.useCallback(async () => {
    try { const d = await ClearCastAPI.reviewGet(token); setState({ loading: false, data: d }); }
    catch (error) { setState({ loading: false, error: error.message || "This review link is no longer valid." }); }
  }, [token]);
  React.useEffect(() => { load(); }, [load]);

  const act = async (action) => {
    setBusy(action);
    try {
      await ClearCastAPI.reviewAction(token, { action, note, reviewer: reviewer.trim() || "Client reviewer" });
      setDone(action === "approve" ? "Approved — thank you!" : action === "reject" ? "Sent back as rejected." : "Changes requested.");
      setNote("");
      await load();
    } catch (error) { setState((s) => ({ ...s, error: error.message })); }
    finally { setBusy(""); }
  };

  if (state.loading) return <div className="review-shell"><div className="card" style={{ padding: 24 }}>Loading review…</div></div>;
  if (state.error) return <div className="review-shell"><div className="card" style={{ padding: 24 }}>{state.error}</div></div>;
  const { businessName, post } = state.data;
  const wl = state.data.whiteLabel;
  const markText = wl && wl.enabled && wl.appName ? wl.appName.slice(0, 2).toUpperCase() : (businessName || "CC").slice(0, 2).toUpperCase();
  const markColor = wl && wl.enabled && wl.accentColor ? wl.accentColor : (state.data.brandColor || "");
  const statusLabel = { needs_approval: "Awaiting your review", scheduled: "Approved & scheduled", changes_requested: "Changes requested", rejected: "Rejected", published: "Published" }[post.status] || post.status;

  return (
    <div className="review-shell">
      <div className="review-card">
        <div className="review-head">
          <div className="brand-mark" style={{ width: 36, height: 36, borderRadius: 10, background: markColor || undefined }}>{markText}</div>
          <div>
            <div style={{ fontWeight: 700 }}>{businessName}</div>
            <div className="muted" style={{ fontSize: 12 }}>asked you to review a post · {statusLabel}</div>
          </div>
        </div>

        <div className="review-post">
          <div style={{ display: "flex", gap: 6, marginBottom: 10, flexWrap: "wrap" }}>
            {(post.platforms || []).map((id) => <PlatformIcon key={id} id={id} size={20} />)}
            <span className="muted" style={{ fontSize: 12, marginLeft: 4 }}>{post.date} · {post.time}</span>
          </div>
          <div style={{ whiteSpace: "pre-wrap", fontSize: 14, lineHeight: 1.55 }}>{post.caption || "(no caption)"}</div>
          {(post.media || []).length > 0 && (
            <div className="muted" style={{ fontSize: 12, marginTop: 10 }}>{post.media.length} media attachment{post.media.length === 1 ? "" : "s"}</div>
          )}
        </div>

        {done ? (
          <div className="review-done">{done} <button className="btn btn-sm" onClick={() => setDone("")} style={{ marginLeft: 8 }}>Review again</button></div>
        ) : (
          <div className="review-actions">
            <input className="input" placeholder="Your name (optional)" value={reviewer} onChange={(e) => setReviewer(e.target.value)} />
            <textarea className="input" placeholder="Add a note or feedback (optional)" value={note} onChange={(e) => setNote(e.target.value)} style={{ minHeight: 64, resize: "vertical" }} />
            <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
              <button className="btn btn-primary" disabled={!!busy} onClick={() => act("approve")}>{busy === "approve" ? "Saving…" : "Approve"}</button>
              <button className="btn" disabled={!!busy} onClick={() => act("changes")}>{busy === "changes" ? "Saving…" : "Request changes"}</button>
              <button className="btn" disabled={!!busy} onClick={() => act("reject")} style={{ color: "var(--danger)" }}>{busy === "reject" ? "Saving…" : "Reject"}</button>
            </div>
          </div>
        )}
        <div className="muted" style={{ fontSize: 11, marginTop: 16, textAlign: "center" }}>No account needed — your review goes straight to the {businessName} team.</div>
      </div>
    </div>
  );
}

const PAGE_TITLES = {
  onboarding: "Get Started",
  home: "Home",
  plan: "Plan",
  engage: "Engage",
  analyze: "Analyze",
  grow: "Grow",
  create: "Create Post",
  calendar: "Calendar",
  published: "Published & Live",
  inbox: "Inbox",
  listening: "Listening",
  reviews: "Reviews",
  analytics: "Analytics",
  experiments: "A/B Tests",
  benchmarking: "Benchmarks",
  ads: "Ads",
  advocacy: "Advocacy & UGC",
  approvals: "Approvals",
  studio: "Creator Studio",
  media: "Media Library",
  linkbio: "Link in Bio",
  links: "Short Links",
  reports: "Reports",
  bulk: "Bulk Upload",
  content: "Recycle & RSS",
  team: "Team",
  integrations: "Integrations",
  developer: "Developer / API",
  agency: "Agency & White-label",
  billing: "Billing & Plan",
  businesses: "Manage Businesses",
  status: "System Status",
  admin: "Admin Console",
  settings: "Settings",
};

function App() {
  const bioHandle = window.location.pathname.match(/^\/bio\/([^/]+)/)?.[1];
  const reviewToken = window.location.pathname.match(/^\/review\/([^/]+)/)?.[1];
  const initialPage = (() => {
    if (reviewToken) return "review-public";
    if (bioHandle) return "bio-public";
    const hash = window.location.hash.slice(1);
    if (PAGE_TITLES[hash]) return hash;
    return "home";
  })();
  const [page, setPage] = React.useState(initialPage);
  const [data, setData] = React.useState(null);
  const [bootErr, setBootErr] = React.useState(false);
  const [workspaces, setWorkspaces] = React.useState({ workspaces: [], activeWorkspaceId: null });
  const [notice, setNotice] = React.useState("");
  const exportLockRef = React.useRef(false);
  // Content-ownership gate before a VIDEO upload (rights/copyright attestation).
  const [ownPrompt, setOwnPrompt] = React.useState(null); // { count, resolve } | null
  const confirmContentOwnership = React.useCallback((count) => {
    return new Promise((resolve) => setOwnPrompt({ count, resolve }));
  }, []);
  const answerOwnership = React.useCallback((ok) => {
    setOwnPrompt((cur) => { if (cur && cur.resolve) cur.resolve(ok); return null; });
  }, []);

  const refresh = React.useCallback(async () => {
    const next = await ClearCastAPI.bootstrap();
    setData(next);
    if (next && Array.isArray(next.workspaces)) {
      setWorkspaces({ workspaces: next.workspaces, activeWorkspaceId: next.activeWorkspaceId });
    }
    return next;
  }, []);

  const toast = React.useCallback((message) => {
    setNotice(message);
    window.clearTimeout(window.__shareHubToastTimer);
    window.__shareHubToastTimer = window.setTimeout(() => setNotice(""), 2600);
  }, []);

  // Safety net: many buttons fire actions without their own catch — a server
  // rejection (permission denied, validation, network) previously produced NO
  // feedback at all: the button just un-busied and the user assumed it worked.
  // Surface every otherwise-unhandled async failure as a toast.
  React.useEffect(() => {
    const onRejection = (e) => {
      const msg = (e && e.reason && e.reason.message) || "";
      if (msg) { toast(msg); e.preventDefault(); }
    };
    window.addEventListener("unhandledrejection", onRejection);
    return () => window.removeEventListener("unhandledrejection", onRejection);
  }, [toast]);

  // Action endpoints return the bare workspace envelope (no isAdmin / permissions /
  // auth — those are attached only by /api/bootstrap). Full-replacing on every action
  // would drop them, re-gating admins behind the X "Starter" upsell and hiding admin
  // UI. Merge: keep the last-known bootstrap-only fields when the response omits them.
  const BOOTSTRAP_ONLY_FIELDS = ["isAdmin", "authUser", "authMode", "googleAuthEnabled", "signupsOpen", "currentPermissions", "permissionCatalog", "billingPlans", "stripeConnected", "integrationProviders"];
  const applyData = React.useCallback((next) => {
    if (!next) return;
    setData((prev) => {
      if (!prev) return next;
      const merged = { ...next };
      for (const k of BOOTSTRAP_ONLY_FIELDS) if (merged[k] === undefined && prev[k] !== undefined) merged[k] = prev[k];
      return merged;
    });
  }, []);

  React.useEffect(() => {
    refresh().then(() => setBootErr(false)).catch((error) => {
      console.error(error);
      setBootErr(true);
      toast("Could not load ClearCast data");
    });
  }, [refresh, toast]);

  React.useEffect(() => {
    if (page === "bio-public" || page === "review-public") return;
    if (window.location.pathname.startsWith("/auth/callback")) return;
    window.location.hash = page;
    window.scrollTo(0, 0);
  }, [page]);

  React.useEffect(() => {
    const onHash = () => {
      const h = window.location.hash.slice(1);
      if (PAGE_TITLES[h] && h !== page) setPage(h);
    };
    window.addEventListener("hashchange", onHash);
    return () => window.removeEventListener("hashchange", onHash);
  }, [page]);

  // Background polling — silently re-fetches state every 90s so published/failed
  // posts surfaced by the cron appear without a manual reload.
  React.useEffect(() => {
    const tick = () => {
      if (document.visibilityState === "hidden") return;
      refresh().catch(() => {});
    };
    const id = setInterval(tick, 90000);
    const onVisible = () => { if (document.visibilityState === "visible") refresh().catch(() => {}); };
    document.addEventListener("visibilitychange", onVisible);
    return () => { clearInterval(id); document.removeEventListener("visibilitychange", onVisible); };
  }, [refresh]);

  // First-run: a brand-new/empty business (no channels, no posts) lands on the
  // guided onboarding checklist instead of a blank composer. Re-runs when the
  // active business changes. Respects a per-business "skip" flag.
  React.useEffect(() => {
    if (!data || !data.activeWorkspaceId || bioHandle) return;
    const empty = (data.integrations || []).filter((integration) => integration.connected).length === 0 && (data.posts || []).length === 0;
    let skipped = {};
    try { skipped = JSON.parse(window.localStorage.getItem("clearcast-onboard-skip") || "{}"); } catch (error) { skipped = {}; }
    if (empty && !skipped[data.activeWorkspaceId]) {
      setPage("onboarding");
    } else if (page === "onboarding" && (!empty)) {
      setPage("create");
    }
  }, [data && data.activeWorkspaceId]);

  const renderPage = () => {
    if (page === "bio-public") return <PublicBioPage handle={decodeURIComponent(bioHandle || "")} />;
    switch (page) {
      case "onboarding": return <OnboardingPage />;
      case "home": return <HomeHub />;
      case "plan": return <PlanHub />;
      case "engage": return <EngageHub />;
      case "analyze": return <AnalyzeHub />;
      case "grow": return <GrowHub />;
      case "create": return <CreatePostPage />;
      case "calendar": return <CalendarPage />;
      case "published": return <PublishedPage />;
      case "inbox": return <InboxPage />;
      case "listening": return <ListeningPage />;
      case "reviews": return <ReviewsPage />;
      case "analytics": return <AnalyticsPage />;
      case "experiments": return <ExperimentsPage />;
      case "benchmarking": return <BenchmarkingPage />;
      case "ads": return <AdsPage />;
      case "advocacy": return <AdvocacyPage />;
      case "approvals": return <ApprovalsPage />;
      case "studio": return <StudioPage />;
      case "media": return <MediaLibraryPage />;
      case "linkbio": return <LinkInBioPage />;
      case "links": return <LinksPage />;
      case "reports": return <ReportsPage />;
      case "bulk": return <BulkUploadPage />;
      case "content": return <ContentPage />;
      case "team": return <TeamPage />;
      case "integrations": return <IntegrationsPage />;
      case "developer": return <DeveloperPage />;
      case "agency": return <AgencyPage />;
      case "billing": return <BillingPage />;
      case "businesses": return <BusinessesPage />;
      case "status": return <StatusPage />;
      case "admin": return <AdminPage />;
      case "settings": return <SettingsHub />;
      default: return <HomeHub />;
    }
  };

  const actions = React.useMemo(() => ({
    async createPost(payload) {
      const result = await ClearCastAPI.createPost(payload);
      applyData(result.data);
      toast(payload.status === "draft" || payload.saveAsDraft ? "Draft saved" : payload.requireApproval === false ? "Post scheduled" : "Post sent for approval");
      return result.post;
    },
    async updatePost(id, payload) {
      const result = await ClearCastAPI.updatePost(id, payload);
      applyData(result.data);
      toast("Post updated");
      return result.post;
    },
    async deletePost(id) {
      const result = await ClearCastAPI.deletePost(id);
      applyData(result.data);
      toast("Post deleted");
      return result.deleted;
    },
    async duplicatePost(id) {
      const result = await ClearCastAPI.duplicatePost(id);
      applyData(result.data);
      toast("Post duplicated as draft");
      return result.post;
    },
    async saveWorkspace(payload) {
      const result = await ClearCastAPI.saveWorkspace(payload);
      applyData(result.data);
      toast("Settings saved");
      return result.workspace;
    },
    async resetDemoData() {
      const result = await ClearCastAPI.resetDemoData();
      applyData(result.data);
      toast("Demo workspace reset");
      return result.data;
    },
    async clearWorkspace() {
      const result = await ClearCastAPI.clearWorkspaceData();
      applyData(result.data);
      toast("Business cleared — fresh start");
      return result.data;
    },
    async uploadMedia(file, folder = "all", opts = {}) {
      // Rights gate: before a user-supplied VIDEO uploads, require an ownership
      // attestation. Generated content (quote graphics, AI media) passes
      // opts.generated and skips it. The acknowledgment is timestamped on the
      // asset server-side for audit.
      const isVideo = /^video\//i.test(file && file.type || "") || /\.(mp4|mov|webm|m4v|avi|mkv)$/i.test(file && file.name || "");
      let rightsAck;
      if (isVideo && !opts.generated) {
        const ok = await confirmContentOwnership(1);
        if (!ok) { const e = new Error("Upload cancelled — content ownership not confirmed."); e.cancelled = true; throw e; }
        rightsAck = new Date().toISOString();
      }
      const result = await ClearCastAPI.uploadMedia(file, folder);
      if (rightsAck && result.asset && result.asset.id) {
        try { await ClearCastAPI.updateMedia(result.asset.id, { rightsConfirmedAt: rightsAck }); } catch (e) { /* non-fatal */ }
      }
      applyData(result.data);
      toast("Media uploaded");
      return result.asset;
    },
    async updateMedia(id, payload) {
      const result = await ClearCastAPI.updateMedia(id, payload);
      applyData(result.data);
      return result.asset;
    },
    async updateContact(key, payload) {
      const result = await ClearCastAPI.updateContact(key, payload);
      applyData(result.data);
      toast("Contact updated");
      return result.meta;
    },
    async deleteMedia(id) {
      const result = await ClearCastAPI.deleteMedia(id);
      applyData(result.data);
      toast("Media deleted");
      return result.deleted;
    },
    async bulkImport(csv) {
      const result = await ClearCastAPI.bulkImport(csv);
      applyData(result.data);
      toast(`Imported ${result.imported} posts${result.skipped ? `, skipped ${result.skipped}` : ""}`);
      return result.posts;
    },
    async bulkValidate(csv) {
      const result = await ClearCastAPI.bulkValidate(csv);
      toast(result.invalidRows?.length ? `${result.invalidRows.length} rows need attention` : "CSV is ready to import");
      return result;
    },
    async aiCaption(caption, tone, voice = "") {
      const result = await ClearCastAPI.aiCaption(caption, tone, voice);
      toast(`AI caption generated · ${result.model || "workspace voice"}`);
      return result.caption;
    },
    async aiVideoScript(payload) {
      const result = await ClearCastAPI.aiVideoScript(payload);
      toast(result.simulated ? "Script template ready (add an AI key for tailored scripts)" : "Video script generated");
      return result;
    },
    async aiAltText(payload) {
      const result = await ClearCastAPI.aiAltText(payload);
      if (result.data) applyData(result.data);
      toast("Alt text suggested");
      return result;
    },
    async aiRepurpose(payload) {
      const result = await ClearCastAPI.aiRepurpose(payload);
      toast(result.simulated ? "Sample pack ready (add an AI key for tailored posts)" : "Post pack generated");
      return result;
    },
    async changePlan(plan) {
      const result = await ClearCastAPI.changePlan(plan);
      applyData(result.data);
      toast("Plan updated");
      return result.billing;
    },
    async cancelBilling() {
      const result = await ClearCastAPI.cancelBilling();
      applyData(result.data);
      toast("Subscription canceled — no further charges");
      return result.billing;
    },
    async reactivateBilling() {
      const result = await ClearCastAPI.reactivateBilling();
      applyData(result.data);
      toast("Subscription reactivated");
      return result.billing;
    },
    async startCheckout(plan) {
      const result = await ClearCastAPI.billingCheckout(plan);
      if (result && result.url) { window.location.href = result.url; return result; }
      toast(result && result.message ? result.message : "Card payments aren't connected yet");
      return result;
    },
    async openBillingPortal() {
      const result = await ClearCastAPI.billingPortal();
      if (result && result.url) { window.location.href = result.url; return result; }
      toast(result && result.message ? result.message : "Card payments aren't connected yet");
      return result;
    },
    async setAiBudget(monthlyBudgetUsd) {
      const result = await ClearCastAPI.setAiBudget(monthlyBudgetUsd);
      applyData(result.data);
      toast(monthlyBudgetUsd == null ? "AI budget cap removed" : `AI budget set to $${Number(monthlyBudgetUsd).toFixed(2)}/mo`);
      return result.usage;
    },
    async addStock(payload) {
      const result = await ClearCastAPI.addStock(payload);
      applyData(result.data);
      toast("Stock photo added");
      return result.asset;
    },
    async addMediaLink(payload) {
      const result = await ClearCastAPI.addMediaLink(payload);
      applyData(result.data);
      toast("Media added");
      return result.asset;
    },
    searchGiphy(q) {
      return ClearCastAPI.searchGiphy(q);
    },
    async generateImage(payload) {
      const result = await ClearCastAPI.generateImage(payload);
      applyData(result.data);
      const spend = result.usage ? ` · $${result.usage.spentThisMonth.toFixed(2)} used this month` : "";
      toast((result.simulated ? "AI image generated (preview mode)" : `AI image generated (${result.provider})`) + spend);
      return result.asset;
    },
    async generateVariants(payload) {
      const result = await ClearCastAPI.generateVariants(payload);
      if (result.data) applyData(result.data);
      toast(result.simulated ? "Tailored per-platform drafts (preview mode)" : `Tailored per-platform drafts (${result.provider})`);
      return result.variants || {};
    },
    async generateVideo(payload, onProgress) {
      const result = await ClearCastAPI.generateVideo(payload, onProgress);
      if (result.data) applyData(result.data);
      if (!result.asset) throw new Error("Video finished but could not be saved — try again");
      toast("AI video generated");
      return result.asset;
    },
    async hashtagSuggestions(payload) {
      const result = await ClearCastAPI.hashtagSuggestions(payload);
      return result.suggestions || [];
    },
    async updateHashtagGroup(id, payload) {
      const result = await ClearCastAPI.updateHashtagGroup(id, payload);
      applyData(result.data);
      toast("Hashtag group saved");
      return result.group;
    },
    async createHashtagGroup(payload) {
      const result = await ClearCastAPI.createHashtagGroup(payload);
      applyData(result.data);
      toast("Hashtag group created");
      return result.group;
    },
    async bestTimes(payload) {
      const result = await ClearCastAPI.bestTimes(payload);
      toast("Best-time recommendations refreshed");
      return result.recommendations || [];
    },
    async reply(messageId, text) {
      const result = await ClearCastAPI.reply(messageId, text);
      applyData(result.data);
      toast("Reply sent");
      return result.message;
    },
    async updateInbox(messageId, payload, message = "Conversation updated") {
      const result = await ClearCastAPI.updateInbox(messageId, payload);
      applyData(result.data);
      toast(message);
      return result.message;
    },
    async approve(postId) {
      const result = await ClearCastAPI.approve(postId);
      applyData(result.data);
      toast("Post approved and scheduled");
      return result.post;
    },
    async login(email, password) {
      const res = await ClearCastAPI.authLogin(email, password);
      if (res && res.mfaRequired) return { mfaRequired: true };  // caller prompts for the 2FA code
      await refresh();
      toast("Signed in");
      return { ok: true };
    },
    async mfaLogin(email, password, code) {
      await ClearCastAPI.authMfaLogin(email, password, code);
      await refresh();
      toast("Signed in");
    },
    async mfaBegin(password) {
      return await ClearCastAPI.authMfaBegin(password);
    },
    async mfaActivate(password, factorId, code) {
      await ClearCastAPI.authMfaActivate(password, factorId, code);
      await refresh();
      toast("Two-factor authentication is on");
    },
    async mfaDisable(password) {
      await ClearCastAPI.authMfaDisable(password);
      await refresh();
      toast("Two-factor authentication turned off");
    },
    async deleteAccount(password, confirm) {
      await ClearCastAPI.authDeleteAccount(password, confirm);
      window.location.href = "/"; // session is cleared server-side; return to landing
    },
    async signup(payload) {
      await ClearCastAPI.authSignup(payload);
      await refresh();
      toast("Welcome to ClearCast");
    },
    async logout() {
      await ClearCastAPI.authLogout();
      await refresh();
      toast("Signed out");
    },
    async completeGoogleLogin(accessToken) {
      await ClearCastAPI.authGoogleToken(accessToken);
      await refresh();
      toast("Signed in with Google");
    },
    async setWhiteLabel(payload) {
      const result = await ClearCastAPI.setWhiteLabel(payload);
      applyData(result.data);
      toast(payload.enabled ? "White-label branding saved" : "White-label turned off");
      return result.whiteLabel;
    },
    async addAdvocacy(payload) {
      const result = await ClearCastAPI.addAdvocacy(payload);
      applyData(result.data);
      toast("Added to advocacy feed");
      return result.item;
    },
    async shareAdvocacy(id) {
      const result = await ClearCastAPI.shareAdvocacy(id);
      applyData(result.data);
      toast("Marked as shared");
      return result.item;
    },
    async deleteAdvocacy(id) {
      const result = await ClearCastAPI.deleteAdvocacy(id);
      applyData(result.data);
      toast("Removed");
      return result;
    },
    async addCreator(payload) {
      const result = await ClearCastAPI.addCreator(payload);
      applyData(result.data);
      toast(`Added ${payload.name}`);
      return result.creator;
    },
    async updateCreator(id, payload) {
      const result = await ClearCastAPI.updateCreator(id, payload);
      applyData(result.data);
      toast("Creator updated");
      return result.creator;
    },
    async deleteCreator(id) {
      const result = await ClearCastAPI.deleteCreator(id);
      applyData(result.data);
      toast("Creator removed");
      return result;
    },
    async createAdCampaign(payload) {
      try { const result = await ClearCastAPI.createAdCampaign(payload); applyData(result.data); toast("Campaign launched"); return result.campaign; }
      catch (e) { toast(e.message || "Couldn't launch campaign"); throw e; }
    },
    async toggleAdCampaign(id) {
      try { const result = await ClearCastAPI.toggleAdCampaign(id); applyData(result.data); toast(result.campaign.status === "active" ? "Campaign resumed" : "Campaign paused"); return result.campaign; }
      catch (e) { toast(e.message || "Couldn't update campaign"); throw e; }
    },
    async deleteAdCampaign(id) {
      try { const result = await ClearCastAPI.deleteAdCampaign(id); applyData(result.data); toast("Campaign deleted"); return result; }
      catch (e) { toast(e.message || "Couldn't delete campaign"); throw e; }
    },
    async addCompetitor(payload) {
      try { const result = await ClearCastAPI.addCompetitor(payload); applyData(result.data); toast(`Tracking ${payload.name}`); return result.competitor; }
      catch (e) { toast(e.message || "Couldn't add competitor"); throw e; }
    },
    async deleteCompetitor(id) {
      try { const result = await ClearCastAPI.deleteCompetitor(id); applyData(result.data); toast("Competitor removed"); return result; }
      catch (e) { toast(e.message || "Couldn't remove competitor"); throw e; }
    },
    async respondToReview(id, text) {
      const result = await ClearCastAPI.respondToReview(id, text);
      applyData(result.data);
      toast("Response posted");
      return result.review;
    },
    async addListening(payload) {
      const result = await ClearCastAPI.addListening(payload);
      applyData(result.data);
      toast(`Now tracking "${payload.keyword}"`);
      return result.stream;
    },
    async refreshListening(id) {
      const result = await ClearCastAPI.refreshListening(id);
      applyData(result.data);
      toast("Mentions refreshed");
      return result.stream;
    },
    async deleteListening(id) {
      const result = await ClearCastAPI.deleteListening(id);
      applyData(result.data);
      toast("Stopped tracking");
      return result;
    },
    async createApiKey(payload) {
      const result = await ClearCastAPI.createApiKey(payload);
      applyData(result.data);
      toast("API key created");
      return result.apiKey;
    },
    async deleteApiKey(id) {
      const result = await ClearCastAPI.deleteApiKey(id);
      applyData(result.data);
      toast("API key revoked");
      return result;
    },
    async addWebhook(payload) {
      const result = await ClearCastAPI.addWebhook(payload);
      applyData(result.data);
      toast("Webhook added");
      return result.webhook;
    },
    async deleteWebhook(id) {
      const result = await ClearCastAPI.deleteWebhook(id);
      applyData(result.data);
      toast("Webhook removed");
      return result;
    },
    async testWebhook(id) {
      await ClearCastAPI.testWebhook(id);
      toast("Test event sent");
    },
    async addEvergreen(payload) {
      const result = await ClearCastAPI.addEvergreen(payload);
      applyData(result.data);
      toast("Added to evergreen library");
      return result.item;
    },
    async deleteEvergreen(id) {
      const result = await ClearCastAPI.deleteEvergreen(id);
      applyData(result.data);
      toast("Removed from evergreen");
      return result;
    },
    async queueEvergreen(id) {
      const result = await ClearCastAPI.queueEvergreen(id);
      applyData(result.data);
      toast("Recycled into a draft post");
      return result.post;
    },
    async addRssFeed(payload) {
      const result = await ClearCastAPI.addRssFeed(payload);
      applyData(result.data);
      toast("Feed added");
      return result.feed;
    },
    async deleteRssFeed(id) {
      const result = await ClearCastAPI.deleteRssFeed(id);
      applyData(result.data);
      toast("Feed removed");
      return result;
    },
    async importRssFeed(id) {
      const result = await ClearCastAPI.importRssFeed(id);
      applyData(result.data);
      toast(result.imported ? `Imported ${result.imported} draft${result.imported === 1 ? "" : "s"} from feed` : (result.note || "No new items"));
      return result;
    },
    async createExperiment(payload) {
      const result = await ClearCastAPI.createExperiment(payload);
      applyData(result.data);
      toast("A/B test started");
      return result.experiment;
    },
    async concludeExperiment(id) {
      const result = await ClearCastAPI.concludeExperiment(id);
      applyData(result.data);
      toast(`Test concluded — variant ${result.experiment.winner} won`);
      return result.experiment;
    },
    async deleteExperiment(id) {
      const result = await ClearCastAPI.deleteExperiment(id);
      applyData(result.data);
      toast("Test deleted");
      return result;
    },
    async saveSavedReply(payload) {
      const result = await ClearCastAPI.saveSavedReply(payload);
      applyData(result.data);
      toast("Saved reply added");
      return result.reply;
    },
    async deleteSavedReply(id) {
      const result = await ClearCastAPI.deleteSavedReply(id);
      applyData(result.data);
      toast("Saved reply removed");
      return result;
    },
    async createLink(payload) {
      let result;
      try { result = await ClearCastAPI.createLink(payload); }
      catch (e) { toast(e.message || "Couldn't create link"); throw e; }
      applyData(result.data);
      const url = window.location.origin + result.shortPath;
      try { await navigator.clipboard.writeText(url); toast("Short link created & copied"); }
      catch (error) { toast("Short link created"); }
      return result;
    },
    async deleteLink(id) {
      try { const result = await ClearCastAPI.deleteLink(id); applyData(result.data); toast("Link deleted"); return result; }
      catch (e) { toast(e.message || "Couldn't delete link"); throw e; }
    },
    async getReviewLink(postId) {
      const result = await ClearCastAPI.reviewLink(postId);
      const url = window.location.origin + result.path;
      try { await navigator.clipboard.writeText(url); toast("Client review link copied — no login needed"); }
      catch (error) { toast(`Review link: ${url}`); }
      return url;
    },
    async approvalAction(postId, action, note = "") {
      const result = await ClearCastAPI.approvalAction(postId, action, note);
      applyData(result.data);
      const labels = { approve: "Post approved and scheduled", reject: "Post rejected", changes: "Changes requested" };
      toast(labels[action] || "Approval updated");
      return result.post;
    },
    async approvalComment(postId, text) {
      const result = await ClearCastAPI.approvalComment(postId, text);
      applyData(result.data);
      toast("Comment added");
      return result.comment;
    },
    async publishPost(postId, retry = false) {
      try {
        const result = retry ? await ClearCastAPI.retryPublish(postId) : await ClearCastAPI.publishPost(postId);
        applyData(result.data);
        const att = result.attempt;
        const nm = (p) => (window.Platforms && window.Platforms[p] && window.Platforms[p].name) || p;
        const names = (att && Array.isArray(att.permalinks) ? att.permalinks : []).map((p) => nm(p.platform));
        const failed = (att && Array.isArray(att.results) ? att.results : []).filter((r) => r.status === "failed" || r.status === "blocked");
        const failNames = failed.map((r) => nm(r.platform));
        // Surface WHY a channel didn't post + the one action to fix it (not a raw API error).
        const firstHuman = (failed.find((r) => r.human) || {}).human;
        const why = firstHuman ? ` — ${firstHuman.reason}${firstHuman.fix ? " " + firstHuman.fix : ""}` : "";
        if (att && att.status === "blocked") toast(`Publishing paused: ${firstHuman ? firstHuman.reason : "outage policy"}`);
        else if (att && att.status === "partial") toast(`Published to ${names.join(", ")}. Couldn't post to ${failNames.join(", ")}${why}`);
        else if (att && att.status === "published") toast(names.length ? `Published to ${names.join(", ")} 🎉` : "Published 🎉");
        else toast(`Publish failed${why || (att && att.issues && att.issues[0] ? ": " + att.issues[0] : "")}`);
        return result.post;
      } catch (error) {
        const fresh = await ClearCastAPI.bootstrap();
        setData(fresh);
        toast(retry ? "Retry failed readiness checks" : "Publish failed readiness checks");
        throw error;
      }
    },
    async runDuePublishQueue() {
      const result = await ClearCastAPI.runDuePublishQueue();
      applyData(result.data);
      toast(`Queue run complete: ${result.published} published, ${result.failed} failed${result.blocked ? `, ${result.blocked} blocked` : ""}`);
      return result;
    },
    async updateTask(id, payload) {
      const result = await ClearCastAPI.updateTask(id, payload);
      applyData(result.data);
      toast(payload.dismissed ? "Task dismissed" : payload.snoozedUntil ? "Task snoozed" : "Task updated");
      return result.task;
    },
    async restoreTask(id) {
      const result = await ClearCastAPI.restoreTask(id);
      applyData(result.data);
      toast("Task restored");
      return result.restored;
    },
    async addBioLink(title, url) {
      const result = await ClearCastAPI.addBioLink(title, url);
      applyData(result.data);
      toast("Link added");
      return result.link;
    },
    async saveBioPage(payload) {
      const result = await ClearCastAPI.saveBioPage(payload);
      applyData(result.data);
      toast("Bio page saved");
      return result.page;
    },
    async updateBioLink(id, payload) {
      const result = await ClearCastAPI.updateBioLink(id, payload);
      applyData(result.data);
      toast("Bio link updated");
      return result.link;
    },
    async deleteBioLink(id) {
      const result = await ClearCastAPI.deleteBioLink(id);
      applyData(result.data);
      toast("Bio link deleted");
      return result.deleted;
    },
    async clickBioLink(id) {
      const result = await ClearCastAPI.clickBioLink(id);
      applyData(result.data);
      toast("Preview click recorded");
      return result.link;
    },
    async connectIntegration(id) {
      const result = await ClearCastAPI.connectIntegration(id);
      if (result.oauthUrl) { window.location.href = result.oauthUrl; return null; }
      applyData(result.data);
      toast(result.demo ? "Channel connected (demo — add platform keys for live posting)" : "Channel connected");
      return result.integration;
    },
    async connectMastodon(instance) {
      const result = await ClearCastAPI.connectMastodon(instance);
      if (result.oauthUrl) { window.location.href = result.oauthUrl; return null; }
      return result;
    },
    async linkInstagram() {
      const result = await ClearCastAPI.linkInstagram();
      applyData(result.data);
      toast("Instagram connected from your Facebook Page");
      return result.integration;
    },
    async selectFacebookPage(pageId) {
      const result = await ClearCastAPI.selectFacebookPage(pageId);
      applyData(result.data);
      toast("Facebook Page connected");
      return result.integration;
    },
    async toggleIntegration(id) {
      const result = await ClearCastAPI.toggleIntegration(id);
      applyData(result.data);
      toast(result.integration?.connected ? "Integration connected" : "Integration disconnected");
      return result.integration;
    },
    async updateIntegration(id, payload) {
      const result = await ClearCastAPI.updateIntegration(id, payload);
      applyData(result.data);
      toast("Integration updated");
      return result.integration;
    },
    async syncIntegration(id) {
      const result = await ClearCastAPI.syncIntegration(id);
      applyData(result.data);
      toast("Integration sync complete");
      return result.integration;
    },
    async reconnectIntegration(id) {
      const result = await ClearCastAPI.reconnectIntegration(id);
      applyData(result.data);
      toast("Integration reconnected");
      return result.integration;
    },
    async createReport(payload) {
      const result = await ClearCastAPI.createReport(payload);
      applyData(result.data);
      toast("Report created");
      return result.report;
    },
    exportReports(filters = {}) {
      if (exportLockRef.current) return;
      exportLockRef.current = true;
      window.location.href = ClearCastAPI.exportUrl(filters);
      toast("Report export started");
      window.setTimeout(() => {
        exportLockRef.current = false;
      }, 1200);
    },
    async switchUser(userId) {
      const result = await ClearCastAPI.switchUser(userId);
      applyData(result.data);
      toast(`Viewing as ${result.user.name}`);
      return result.user;
    },
    async inviteMember(payload) {
      const result = await ClearCastAPI.inviteMember(payload);
      applyData(result.data);
      toast("Invite sent");
      return result.invite;
    },
    async updateMember(id, payload) {
      const result = await ClearCastAPI.updateMember(id, payload);
      applyData(result.data);
      toast("Member access updated");
      return result.member;
    },
    async updateRole(id, permissions) {
      const result = await ClearCastAPI.updateRole(id, permissions);
      applyData(result.data);
      toast("Role permissions saved");
      return result.role;
    },
    async resendInvite(id) {
      const result = await ClearCastAPI.resendInvite(id);
      applyData(result.data);
      toast("Invite resent");
      return result.invite;
    },
    async switchWorkspace(id) {
      const result = await ClearCastAPI.switchWorkspace(id);
      setWorkspaces({ workspaces: result.workspaces, activeWorkspaceId: result.activeWorkspaceId });
      await refresh();
      const name = result.workspaces.find((workspace) => workspace.id === id)?.name || "business";
      toast(`Switched to ${name}`);
      return result;
    },
    async createWorkspace(payload) {
      const result = await ClearCastAPI.createWorkspace(payload);
      // The new business becomes active but is empty — don't let the first-run
      // onboarding redirect yank the user off the page they're on (e.g. Manage
      // Businesses). Mark it acknowledged so they stay put and see it added.
      try {
        const skip = JSON.parse(window.localStorage.getItem("clearcast-onboard-skip") || "{}");
        if (result.activeWorkspaceId) { skip[result.activeWorkspaceId] = true; window.localStorage.setItem("clearcast-onboard-skip", JSON.stringify(skip)); }
      } catch (e) { /* ignore */ }
      setWorkspaces({ workspaces: result.workspaces, activeWorkspaceId: result.activeWorkspaceId });
      await refresh();
      toast(`${payload.name} added — now your active business`);
      return result;
    },
    async renameWorkspace(id, payload) {
      const result = await ClearCastAPI.renameWorkspace(id, payload);
      setWorkspaces({ workspaces: result.workspaces, activeWorkspaceId: result.activeWorkspaceId });
      await refresh();
      toast("Business updated");
      return result;
    },
    async deleteWorkspace(id) {
      const result = await ClearCastAPI.deleteWorkspace(id);
      setWorkspaces({ workspaces: result.workspaces, activeWorkspaceId: result.activeWorkspaceId });
      await refresh();
      toast("Business removed");
      return result;
    },
    refresh
  }), [refresh, toast]);

  React.useEffect(() => {
    const isNavClick = (target) => target.closest(".sidebar .nav-item");
    const isFormControl = (target) => ["INPUT", "TEXTAREA", "SELECT", "OPTION"].includes(target.tagName);
    const textOf = (node) => (node.getAttribute("aria-label") || node.getAttribute("title") || node.innerText || node.textContent || "").replace(/\s+/g, " ").trim();
    const activateTab = (button) => {
      const group = button.closest(".tabs");
      if (!group) return;
      group.querySelectorAll(".tab").forEach((tab) => tab.classList.remove("active"));
      button.classList.add("active");
    };
    const show = (message) => {
      if (message) toast(message);
    };

    const onClick = async (event) => {
      const target = event.target;
      if (isFormControl(target) || isNavClick(target)) return;

      const link = target.closest("a");
      if (link && link.getAttribute("href")?.startsWith("/api/")) {
        return;
      }

      const button = target.closest("button, a");
      if (!button) return;
      const text = textOf(button);
      if (!text) return;

      if (button.classList.contains("tab")) {
        activateTab(button);
        show(`${text.replace(/[0-9]/g, "").trim()} view selected`);
        return;
      }

      if (text === "New Post") {
        setPage("create");
        show("Composer opened");
        return;
      }

      if (text.includes("Export") || text === "CSV") {
        actions.exportReports();
        return;
      }

      // Note: previously a long list of button-text fragments fired reassuring
      // "success" toasts even when nothing actually happened. That was removed —
      // every action must be wired to real behavior with a truthful result, or
      // show nothing. Buttons not yet wired simply do nothing here.
    };

    document.addEventListener("click", onClick);
    return () => document.removeEventListener("click", onClick);
  }, [actions, setPage, toast]);

  if (page === "review-public") {
    return (
      <ClearCastContext.Provider value={{ data, actions, toast, setPage }}>
        <ReviewPage token={decodeURIComponent(reviewToken || "")} />
        <div className={`toast ${notice ? "show" : ""}`}>{notice}</div>
      </ClearCastContext.Provider>
    );
  }

  // OAuth (Google) return leg — capture the session from the URL fragment.
  if (window.location.pathname.startsWith("/auth/callback")) {
    return (
      <ClearCastContext.Provider value={{ data, actions, toast, setPage }}>
        <AuthCallback />
        <div className={`toast ${notice ? "show" : ""}`}>{notice}</div>
      </ClearCastContext.Provider>
    );
  }

  // Public front door (only in password mode, and once data has loaded telling
  // us so): a branded marketing landing with features, pricing packages, and an
  // integrated sign-up / log-in / purchase flow. Falls back to the simple
  // LoginPage if the landing bundle failed to load.
  if (data && data.authMode === "password" && !data.authUser) {
    const FrontDoor = typeof LandingPage === "function" ? LandingPage : LoginPage;
    return (
      <ClearCastContext.Provider value={{ data, actions, toast, setPage }}>
        <FrontDoor />
        <div className={`toast ${notice ? "show" : ""}`}>{notice}</div>
      </ClearCastContext.Provider>
    );
  }

  return (
    <ClearCastContext.Provider value={{ data, actions, toast, setPage }}>
      <div className="app" data-screen-label={PAGE_TITLES[page] || "Bio"}>
        {page !== "bio-public" && <Sidebar page={page} setPage={setPage} data={data} workspaces={workspaces.workspaces} activeWorkspaceId={workspaces.activeWorkspaceId} actions={actions}/>}
        <div className="main">
          {page !== "bio-public" && <Topbar title={PAGE_TITLES[page]} page={page} data={data} actions={actions} setPage={setPage} workspaces={workspaces.workspaces} activeWorkspaceId={workspaces.activeWorkspaceId}/>}
          {data ? renderPage() : (bootErr ? (
            <div className="page"><div className="card" style={{ maxWidth: 460, margin: "40px auto", textAlign: "center", padding: 28 }}>
              <div style={{ fontWeight: 700, fontSize: 16, marginBottom: 6 }}>Can’t reach ClearCast’s servers</div>
              <div className="muted" style={{ fontSize: 13, lineHeight: 1.5, marginBottom: 16 }}>Your connection or a network/Wi-Fi content filter may be blocking this site. Check your internet, try a different network, or open <b>clearcast-rho.vercel.app</b>.</div>
              <button className="btn btn-primary" onClick={() => { setBootErr(false); refresh().catch(() => setBootErr(true)); }}>Retry</button>
            </div></div>
          ) : <div className="page"><div className="card">Loading ClearCast...</div></div>)}
        </div>
        <div className={`toast ${notice ? "show" : ""}`}>{notice}</div>
        {ownPrompt && (
          <div className="modal-backdrop" style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.55)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 9000, padding: 16 }} onClick={() => answerOwnership(false)}>
            <div className="card" style={{ maxWidth: 440, width: "100%", padding: 24 }} onClick={(e) => e.stopPropagation()} role="dialog" aria-modal="true" aria-label="Confirm content rights">
              <div style={{ fontWeight: 700, fontSize: 16, marginBottom: 8 }}>Do you own the rights to this video?</div>
              <div className="muted" style={{ fontSize: 13, lineHeight: 1.55, marginBottom: 18 }}>
                Only upload video you created or have permission to publish. Posting content you don’t own can lead to copyright strikes or account suspension on the connected platforms.
              </div>
              <div style={{ display: "flex", gap: 10, justifyContent: "flex-end" }}>
                <button className="btn" onClick={() => answerOwnership(false)}>Cancel</button>
                <button className="btn btn-primary" onClick={() => answerOwnership(true)}>Yes, I own or have rights to it</button>
              </div>
            </div>
          </div>
        )}
      </div>
    </ClearCastContext.Provider>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
window.useClearCast = useClearCast;
window.useShareHub = useClearCast;
