// studio-views-b.jsx — Analytics, Revenue, Audience, Drafts, Moodboards, Settings
//
// Analytics, Revenue, Audience and Moodboards have no data source, so they are
// honest empty states. Drafts reads the builder's real local draft. Settings
// edits the real Supabase account.

const { useState: useStateVB, useMemo: useMemoVB, useEffect: useEffectVB } = React;

// ─── Analytics / Revenue / Audience ────────────────────────────────────────
// Analytics, Revenue and Audience all measured engagement — views, clicks,
// CTR, conversion, traffic sources, followers, demographics. None of that is
// collected anywhere in the product, so each screen is an honest empty state
// rather than a demo dataset dressed up as the user's own numbers.

// Real engagement, aggregated in SQL from the events table. Everything here
// comes from actual visits to the owner's published outfits — nothing is
// modelled or estimated. Until a published outfit gets traffic, it is empty
// and says so.
const ANALYTICS_RANGES = { '7D': 7, '30D': 30, '90D': 90, '12M': 365 };

function StudioAnalytics() {
  const [range, setRange] = useStateVB('30D');
  const [data, setData] = useStateVB(null);
  const [loading, setLoading] = useStateVB(true);
  const [error, setError] = useStateVB(null);

  const load = async (r) => {
    setLoading(true);
    setError(null);
    const days = ANALYTICS_RANGES[r] || 30;
    const to = new Date();
    const from = new Date(to.getTime() - days * 86400000);
    const [totals, outfits, products, domains, daily] = await Promise.all([
      window.MBAnalytics.totals(from, to),
      window.MBAnalytics.topOutfits(from, to, 8),
      window.MBAnalytics.topProducts(from, to, 8),
      window.MBAnalytics.outboundByDomain(from, to, 8),
      window.MBAnalytics.byDay(from, to),
    ]);
    const firstError = [totals, outfits, products, domains, daily].find(x => x.error);
    if (firstError) { setError(firstError.error); setData(null); setLoading(false); return; }
    setData({
      totals: totals.totals,
      outfits: outfits.rows || [],
      products: products.rows || [],
      domains: domains.rows || [],
      // Zero-filled here rather than in the chart, so the range the user asked
      // for is what gets drawn even on days nothing happened.
      days: window.fillDailySeries(daily.rows || [], from, to),
    });
    setLoading(false);
  };

  useEffectVB(() => { load(range); }, [range]);

  const empty = data && data.totals.views === 0 && data.totals.productClicks === 0 && data.totals.outboundClicks === 0;
  const rangeLabel = range === '12M' ? 'the last 12 months' : 'the last ' + (ANALYTICS_RANGES[range] || 30) + ' days';

  return (
    <div>
      <StudioHeader
        kicker="Studio · Analytics"
        title="Engagement."
        sub="Views and clicks on your published outfits, and where visitors go from them."
        right={<RangeSelect value={range} onChange={setRange} options={['7D', '30D', '90D', '12M']}/>}
      />

      {error && <StudioError error={error} onRetry={() => load(range)}/>}
      {loading && !error && <StudioLoading label="Loading engagement…"/>}

      {!loading && !error && data && (
        <>
          <div className="dc-stat-grid" style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 14, marginBottom: 14 }}>
            <StatCard label="Outfit views"    value={data.totals.views}          sublabel={'in ' + rangeLabel}/>
            <StatCard label="Product clicks"  value={data.totals.productClicks}  sublabel="taps on a piece"/>
            <StatCard label="Clicks to store" value={data.totals.outboundClicks} sublabel="left for the retailer"/>
          </div>

          {empty ? (
            <Card title="No visits yet" sub="Nothing recorded in this range">
              <div style={{ fontSize: 13, color: '#5a5a54', lineHeight: 1.6, maxWidth: '68ch' }}>
                Tracking is live, but none of your published outfits have been visited in {rangeLabel}.
                Share an outfit link and views, product clicks and clicks through to the
                store will appear here. Nothing is estimated — this stays empty until
                somebody actually visits.
              </div>
            </Card>
          ) : (
            <>
              {/* Day by day. Three facets sharing one y-scale — see DailyTrend. */}
              <Card title="Day by day"
                    sub={'Views, product clicks and clicks to the store · ' + rangeLabel}
                    style={{ marginBottom: 14 }}>
                <DailyTrend days={data.days}/>
              </Card>

              {/* Clicks to the store, by product — the metric that matters */}
              <Card title="Clicks to the store, by product"
                    sub="Visitors who left modaBoard for the retailer"
                    style={{ marginBottom: 14 }}>
                {data.products.length === 0 ? (
                  <div style={{ padding: '20px 0', fontSize: 13, color: '#8a8580' }}>
                    No product clicks in this range.
                  </div>
                ) : (
                  <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
                    {data.products.map((p, i) => (
                      <div key={p.product_label + i} style={{
                        display: 'grid', gridTemplateColumns: '1fr 110px 130px', gap: 14, alignItems: 'center',
                        paddingBottom: 10,
                        borderBottom: i < data.products.length - 1 ? '1px solid rgba(20,20,18,0.04)' : 'none',
                      }}>
                        <div style={{ minWidth: 0, fontSize: 13.5, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                          {p.product_label}
                        </div>
                        <Cell label="Taps" value={fmtNum(Number(p.product_clicks || 0))} small/>
                        <Cell label="To store" value={fmtNum(Number(p.outbound_clicks || 0))} small accent/>
                      </div>
                    ))}
                  </div>
                )}
              </Card>

              <div style={{ display: 'grid', gridTemplateColumns: '1.4fr 1fr', gap: 14, marginBottom: 14 }}>
                <Card title="Top outfits" sub={'By views in ' + rangeLabel}>
                  {data.outfits.length === 0 ? (
                    <div style={{ padding: '20px 0', fontSize: 13, color: '#8a8580' }}>No outfit views in this range.</div>
                  ) : (
                    <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
                      {data.outfits.map((o, i) => (
                        <div key={o.outfit_id} style={{
                          display: 'grid', gridTemplateColumns: '24px 1fr 90px 90px 110px', gap: 12, alignItems: 'center',
                          paddingBottom: 10,
                          borderBottom: i < data.outfits.length - 1 ? '1px solid rgba(20,20,18,0.04)' : 'none',
                        }}>
                          <div style={{ fontSize: 16, color: '#8a8580', fontFamily: "'Instrument Serif',serif", fontStyle: 'italic' }}>0{i + 1}</div>
                          <div style={{ minWidth: 0, fontSize: 13.5, fontFamily: "'Instrument Serif',serif", fontStyle: 'italic', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                            {o.title || 'Untitled outfit'}
                          </div>
                          <Cell label="Views" value={fmtNum(Number(o.views || 0))} small/>
                          <Cell label="Taps" value={fmtNum(Number(o.product_clicks || 0))} small/>
                          <Cell label="To store" value={fmtNum(Number(o.outbound_clicks || 0))} small accent/>
                        </div>
                      ))}
                    </div>
                  )}
                </Card>

                <Card title="Where they went" sub="Destination domain">
                  {data.domains.length === 0 ? (
                    <div style={{ padding: '20px 0', fontSize: 13, color: '#8a8580' }}>No clicks to a store yet.</div>
                  ) : (
                    <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
                      {data.domains.map((d) => {
                        const top = Math.max(...data.domains.map(x => Number(x.outbound_clicks || 0)), 1);
                        const n = Number(d.outbound_clicks || 0);
                        return (
                          <div key={d.destination_domain}>
                            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 5 }}>
                              <span style={{ fontSize: 12.5, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{d.destination_domain}</span>
                              <span style={{ fontSize: 12.5, fontVariantNumeric: 'tabular-nums', fontWeight: 500 }}>{fmtNum(n)}</span>
                            </div>
                            <div style={{ height: 5, background: 'rgba(20,20,18,0.06)', borderRadius: 999, overflow: 'hidden' }}>
                              <div style={{ width: (n / top * 100) + '%', height: '100%', background: '#1a1a18', borderRadius: 999 }}/>
                            </div>
                          </div>
                        );
                      })}
                    </div>
                  )}
                </Card>
              </div>
            </>
          )}

          <NotTracked
            title="Still not tracked"
            body="Affiliate revenue, conversion and follower counts need data modaBoard doesn't collect: there is no affiliate network connected, so a click to a store is the last thing we can see."
            metrics={['Affiliate revenue', 'Conversion rate', 'Earnings per outfit', 'Followers']}
          />
        </>
      )}
    </div>
  );
}

function StudioRevenue() {
  return (
    <NotTrackedPage
      kicker="Studio · Revenue"
      panelTitle="Earnings & payouts"
      title="Affiliate revenue."
      sub="No affiliate data is connected."
      body={<>There is no affiliate network connected and no click or order data
        flowing in, so modaBoard cannot report earnings, commissions or payouts.
        Connecting a network and recording attributed orders is what will fill
        this screen — until then it stays empty rather than showing an estimate.</>}
      metrics={['Affiliate revenue', 'Pending earnings', 'Commission rates', 'Payout history', 'Earnings per outfit', 'Revenue by brand']}
    />
  );
}

function StudioAudience() {
  return (
    <NotTrackedPage
      kicker="Studio · Audience"
      panelTitle="Audience metrics"
      title="Audience."
      sub="No audience data is collected."
      body={<>modaBoard has no follower graph and collects no visitor
        analytics, so there is nothing to report about who sees your outfits.
        Follower counts, demographics and location breakdowns will appear here
        if and when that data is actually gathered.</>}
      metrics={['Followers', 'Follower growth', 'Age & gender split', 'Top locations', 'Referring platforms', 'Repeat visitors']}
    />
  );
}

// ─── Drafts ────────────────────────────────────────────────────────────────
// The builder keeps one work-in-progress board in localStorage under
// 'dripcheck.draft'. That is a real (if local-only) source, so it is shown.
// There is no server-side draft store, hence no list of them.
function StudioDrafts({ go }) {
  const [draft, setDraft] = useStateVB(null);
  const [checked, setChecked] = useStateVB(false);

  useEffectVB(() => {
    try {
      const raw = localStorage.getItem('dripcheck.draft');
      setDraft(raw ? JSON.parse(raw) : null);
    } catch { setDraft(null); }
    setChecked(true);
  }, []);

  const discard = () => {
    try { localStorage.removeItem('dripcheck.draft'); } catch {}
    setDraft(null);
  };

  const items = (draft && draft.items) || [];

  return (
    <div>
      <StudioHeader
        kicker="Studio · Drafts"
        title="Work in progress."
        sub="The builder keeps your current unsaved board on this device."
        right={<button onClick={() => go && go({ name: 'dashboard' })} style={btnPrimary}>+ New outfit</button>}
      />

      {!checked ? (
        <StudioLoading label="Checking for a saved draft…"/>
      ) : !draft ? (
        <Card>
          <div style={{ padding: '28px 0', textAlign: 'center' }}>
            <div style={{ fontSize: 14, marginBottom: 6 }}>No draft on this device.</div>
            <div style={{ fontSize: 12.5, color: '#8a8580', marginBottom: 16, lineHeight: 1.6, maxWidth: '52ch', marginInline: 'auto' }}>
              Start building an outfit and your progress is kept here until you save it to your account.
            </div>
            <button onClick={() => go && go({ name: 'dashboard' })} style={{
              appearance: 'none', border: 'none', background: '#1a1a18', color: '#FAFAF7',
              fontSize: 11, padding: '10px 18px', letterSpacing: '0.14em',
              textTransform: 'uppercase', fontWeight: 500, borderRadius: 999,
              cursor: 'pointer', fontFamily: 'inherit',
            }}>Create an outfit</button>
          </div>
        </Card>
      ) : (
        <Card title="Current draft" sub="Stored in this browser only — not synced to your account">
          <div style={{ display: 'grid', gridTemplateColumns: '96px 1fr auto', gap: 18, alignItems: 'center' }}>
            <div style={{ width: 96, height: 96, borderRadius: 10, overflow: 'hidden', background: '#FAFAF7', border: '1px solid rgba(20,20,18,0.06)' }}>
              <OutfitBoard outfit={{ items }}/>
            </div>
            <div style={{ minWidth: 0 }}>
              <div style={{ fontSize: 17, fontFamily: "'Instrument Serif',serif", fontStyle: 'italic' }}>
                {draft.title || 'Untitled draft'}
              </div>
              <div style={{ fontSize: 11.5, color: '#8a8580', marginTop: 5, display: 'flex', gap: 8, flexWrap: 'wrap' }}>
                <span>{items.length} {items.length === 1 ? 'piece' : 'pieces'}</span>
                {draft.mood && <><span>·</span><span>{draft.mood}</span></>}
                {draft.savedAt && <><span>·</span><span>last edited {fmtDay(draft.savedAt)}</span></>}
              </div>
            </div>
            <div style={{ display: 'flex', gap: 8 }}>
              <button onClick={() => go && go({ name: 'dashboard' })} style={btnPrimary}>Open in builder</button>
              <button onClick={discard} style={{ ...linkBtn, color: '#8a8580' }}>Discard</button>
            </div>
          </div>
        </Card>
      )}
    </div>
  );
}

// ─── Moodboards ────────────────────────────────────────────────────────────
function StudioMoodboards() {
  return (
    <NotTrackedPage
      kicker="Studio · Moodboards"
      panelTitle="Moodboards"
      title="Moodboards."
      sub="Not built yet."
      body={<>Moodboards aren’t implemented — there is nowhere to store a board
        of pinned references, so there is nothing to list. The boards that used
        to appear here were assembled from sample outfits, not yours.</>}
      metrics={['Pinned references', 'Themed boards', 'Shared boards']}
    />
  );
}

// ─── Settings ──────────────────────────────────────────────────────────────
// Everything on the Profile tab is the creator's public page at
// /<username> — the same row the profile itself reads, so what they type here
// is what a logged-out visitor sees.
function StudioSettings({ me, onProfileSaved, go }) {
  const [tab, setTab] = useStateVB('profile');
  const [pName, setPName] = useStateVB('');
  const [pBio, setPBio] = useStateVB('');
  const [pLocation, setPLocation] = useStateVB('');
  const [pTags, setPTags] = useStateVB('');
  const [pInstagram, setPInstagram] = useStateVB('');
  const [pTiktok, setPTiktok] = useStateVB('');
  const [pWebsite, setPWebsite] = useStateVB('');
  const [avatarUrl, setAvatarUrl] = useStateVB(null);
  const [avatarBusy, setAvatarBusy] = useStateVB(false);
  const [avatarMsg, setAvatarMsg] = useStateVB(null);
  const fileRef = React.useRef(null);
  const [loaded, setLoaded] = useStateVB(false);
  const [saving, setSaving] = useStateVB(false);
  const [profileMsg, setProfileMsg] = useStateVB(null);
  // The username is the first half of every share URL and has its own rules
  // (reserved words, collisions), so it saves through its own path.
  const [username, setUsername] = useStateVB('');
  const [usernameLoading, setUsernameLoading] = useStateVB(true);
  const [usernameMsg, setUsernameMsg] = useStateVB(null);
  const [usernameBusy, setUsernameBusy] = useStateVB(false);

  const fill = (p) => {
    setPName(p.displayName || '');
    setPBio(p.bio || '');
    setPLocation(p.location || '');
    setPTags((p.tags || []).join(', '));
    setPInstagram(p.instagram || '');
    setPTiktok(p.tiktok || '');
    setPWebsite(p.website || '');
    setAvatarUrl(p.avatarUrl || null);
    setUsername(p.username || '');
  };

  // Upload replaces whatever is there: one object per account, at
  // <user-id>/avatar.<ext>, which is also the only path the storage policies
  // let this account write.
  const pickAvatar = async (e) => {
    const file = e.target.files && e.target.files[0];
    if (e.target) e.target.value = '';          // let the same file be re-picked
    if (!file || avatarBusy) return;
    const complaint = window.MBProfile.checkAvatar(file);
    if (complaint) { setAvatarMsg({ kind: 'error', text: complaint }); return; }
    setAvatarBusy(true);
    setAvatarMsg(null);
    const res = await window.MBProfile.uploadAvatar(file);
    setAvatarBusy(false);
    if (res.error) { setAvatarMsg({ kind: 'error', text: res.error }); return; }
    setAvatarUrl(res.url);
    setAvatarMsg({ kind: 'ok', text: 'Profile picture updated.' });
    if (onProfileSaved) onProfileSaved();
    setTimeout(() => setAvatarMsg(null), 3200);
  };

  const dropAvatar = async () => {
    if (avatarBusy) return;
    setAvatarBusy(true);
    setAvatarMsg(null);
    const res = await window.MBProfile.removeAvatar();
    setAvatarBusy(false);
    if (res.error) { setAvatarMsg({ kind: 'error', text: res.error }); return; }
    setAvatarUrl(null);
    setAvatarMsg({ kind: 'ok', text: 'Back to your initial.' });
    if (onProfileSaved) onProfileSaved();
    setTimeout(() => setAvatarMsg(null), 3200);
  };

  useEffectVB(() => {
    let cancelled = false;
    (async () => {
      const res = await window.MBProfile.mine();
      if (cancelled) return;
      if (res.error) setUsernameMsg({ kind: 'error', text: res.error });
      else if (res.profile) fill(res.profile);
      setUsernameLoading(false);
      setLoaded(true);
    })();
    return () => { cancelled = true; };
  }, []);

  const saveProfile = async () => {
    if (saving) return;
    setSaving(true);
    setProfileMsg(null);
    const res = await window.MBProfile.save({
      displayName: pName, bio: pBio, location: pLocation, tags: pTags,
      instagram: pInstagram, tiktok: pTiktok, website: pWebsite,
    });
    setSaving(false);
    if (res.error) { setProfileMsg({ kind: 'error', text: res.error }); return; }
    fill(res.profile);
    setProfileMsg({ kind: 'ok', text: 'Saved — this is what visitors see on your profile.' });
    if (onProfileSaved) onProfileSaved();
    setTimeout(() => setProfileMsg(null), 3200);
  };

  const saveUsername = async () => {
    if (usernameBusy) return;
    const local = window.MBProfile.validate(username);
    if (local) { setUsernameMsg({ kind: 'error', text: local }); return; }
    setUsernameBusy(true);
    setUsernameMsg(null);
    const res = await window.MBProfile.setUsername(username);
    setUsernameBusy(false);
    if (res.error) { setUsernameMsg({ kind: 'error', text: res.error }); return; }
    setUsername(res.username);
    setUsernameMsg({ kind: 'ok', text: 'Username updated. Existing share links now use it.' });
  };
  const tabs = [
    { id: 'profile',  label: 'Profile' },
    { id: 'affiliate', label: 'Affiliate networks' },
    { id: 'payouts',  label: 'Payouts' },
    { id: 'notifications', label: 'Notifications' },
  ];
  return (
    <div>
      <StudioHeader kicker="Studio · Settings" title="Account."/>
      <div style={{ display: 'grid', gridTemplateColumns: '200px 1fr', gap: 24 }}>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
          {tabs.map(t => (
            <button key={t.id} onClick={() => setTab(t.id)} style={{
              appearance: 'none', border: 'none', textAlign: 'left', cursor: 'pointer',
              padding: '10px 12px', borderRadius: 8, fontSize: 13, fontFamily: 'inherit',
              background: tab === t.id ? '#1a1a18' : 'transparent',
              color: tab === t.id ? '#FAFAF7' : '#1a1a18',
            }}>{t.label}</button>
          ))}
        </div>
        <Card>
          {tab === 'profile' && (
            <div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 16, marginBottom: 24 }}>
                <InitialAvatar name={pName || me.name} username={username || me.handle} size={72} src={avatarUrl}/>
                <div>
                  <div style={{ fontSize: 11, color: '#8a8580', letterSpacing: '0.14em', textTransform: 'uppercase' }}>Profile image</div>
                  <div style={{ marginTop: 8, display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }}>
                    <input ref={fileRef} type="file" accept="image/jpeg,image/png,image/webp,image/gif"
                           onChange={pickAvatar} style={{ display: 'none' }}/>
                    <button style={{ ...btnGhost, opacity: avatarBusy ? 0.6 : 1 }} disabled={avatarBusy}
                            onClick={() => fileRef.current && fileRef.current.click()}>
                      {avatarBusy ? 'Uploading…' : (avatarUrl ? 'Replace photo' : 'Upload photo')}
                    </button>
                    {avatarUrl && (
                      <button style={{ ...linkBtn, color: '#8a8580' }} disabled={avatarBusy} onClick={dropAvatar}>
                        Remove
                      </button>
                    )}
                  </div>
                  <div style={{
                    marginTop: 6, fontSize: 11.5, lineHeight: 1.5, maxWidth: '40ch',
                    color: avatarMsg && avatarMsg.kind === 'error' ? '#a8331a' : '#8a8580',
                  }}>
                    {avatarMsg
                      ? avatarMsg.text
                      : 'JPEG, PNG, WebP or GIF, up to 2MB. Without one you get your initial, tinted from your username.'}
                  </div>
                </div>
              </div>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 }}>
                <Field label="Display name" value={pName} onChange={setPName}/>
                <Field label="Location" value={pLocation} onChange={setPLocation} placeholder="Stockholm"/>
                <div style={{ gridColumn: '1 / -1' }}>
                  <label style={{ display: 'block', fontSize: 11, letterSpacing: '0.16em', textTransform: 'uppercase', color: '#8a8580', marginBottom: 8 }}>
                    Bio <span style={{ letterSpacing: 0, textTransform: 'none', color: '#a8a39d' }}>· {pBio.length}/280</span>
                  </label>
                  <textarea value={pBio} maxLength={280} onChange={(e) => setPBio(e.target.value)} style={{ width: '100%', minHeight: 80, padding: '11px 12px', border: '1px solid rgba(20,20,18,0.14)', borderRadius: 10, fontSize: 13.5, fontFamily: 'inherit', outline: 'none', background: '#fff' }}/>
                </div>
                <div style={{ gridColumn: '1 / -1' }}>
                  <Field label="Style tags" value={pTags} onChange={setPTags} placeholder="minimal, workwear, vintage"/>
                  <div style={{ marginTop: 6, fontSize: 11.5, color: '#8a8580' }}>
                    Comma separated, up to eight. They show as chips on your profile.
                  </div>
                </div>
                <Field label="Instagram" value={pInstagram} onChange={setPInstagram} prefix="@" placeholder="optional"/>
                <Field label="TikTok" value={pTiktok} onChange={setPTiktok} prefix="@" placeholder="optional"/>
                <div style={{ gridColumn: '1 / -1' }}>
                  <Field label="Website" value={pWebsite} onChange={setPWebsite} placeholder="yourstudio.com"/>
                  <div style={{ marginTop: 6, fontSize: 11.5, color: '#8a8580' }}>
                    Shown on your profile as a link. Paste a handle or a full URL — either works.
                  </div>
                </div>
              </div>
              {/* Username — the real one, from the profiles table. */}
              <div style={{ marginTop: 22, paddingTop: 22, borderTop: '1px solid rgba(20,20,18,0.06)' }}>
                <label style={{ display: 'block', fontSize: 11, letterSpacing: '0.16em', textTransform: 'uppercase', color: '#8a8580', marginBottom: 8 }}>
                  Username
                </label>
                <div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }}>
                  <div style={{ display: 'flex', alignItems: 'center', flex: 1, minWidth: 240, border: '1px solid rgba(20,20,18,0.14)', borderRadius: 10, background: '#fff', overflow: 'hidden' }}>
                    <span style={{ padding: '11px 0 11px 12px', fontSize: 13, color: '#8a8580', fontFamily: 'ui-monospace, monospace', whiteSpace: 'nowrap' }}>modaboard.com/</span>
                    <input
                      value={usernameLoading ? '' : username}
                      placeholder={usernameLoading ? 'Loading…' : 'your-name'}
                      disabled={usernameLoading || usernameBusy}
                      onChange={(e) => { setUsername(e.target.value.toLowerCase()); setUsernameMsg(null); }}
                      style={{ flex: 1, minWidth: 0, border: 'none', outline: 'none', background: 'transparent', padding: '11px 12px 11px 0', fontSize: 13.5, fontFamily: 'inherit' }}
                    />
                  </div>
                  <button onClick={saveUsername} disabled={usernameLoading || usernameBusy} style={{ ...btnGhost, opacity: (usernameLoading || usernameBusy) ? 0.6 : 1 }}>
                    {usernameBusy ? 'Saving…' : 'Save username'}
                  </button>
                </div>
                <div style={{ marginTop: 8, fontSize: 11.5, color: usernameMsg && usernameMsg.kind === 'error' ? '#a8331a' : '#8a8580', lineHeight: 1.5 }}>
                  {usernameMsg
                    ? usernameMsg.text
                    : <>Published outfits are shared at <span style={{ fontFamily: 'ui-monospace, monospace', color: '#1a1a18' }}>modaboard.com/{username || 'your-name'}/outfit-name</span>. Changing it changes every existing share link.</>}
                </div>
              </div>
              <div style={{ marginTop: 18, display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' }}>
                <button style={{ ...btnPrimary, opacity: saving || !loaded ? 0.6 : 1 }} disabled={saving || !loaded} onClick={saveProfile}>
                  {saving ? 'Saving…' : 'Save changes'}
                </button>
                {username && (
                  <button style={btnGhost} onClick={() => go && go({ name: 'profile', username })}>
                    View public profile
                  </button>
                )}
                {profileMsg && (
                  <span style={{
                    fontSize: 12, lineHeight: 1.5,
                    color: profileMsg.kind === 'error' ? '#a8331a' : '#1a6b3a',
                    fontFamily: "'Instrument Serif',serif", fontStyle: 'italic',
                  }}>{profileMsg.kind === 'error' ? profileMsg.text : '✓ ' + profileMsg.text}</span>
                )}
              </div>
            </div>
          )}
          {tab === 'affiliate' && (
            <NotTracked
              title="Affiliate networks"
              body={<>No affiliate network integration exists yet, so no account
                is connected and no commission rates are known. The networks
                previously listed here as “Connected”, each with an average
                commission, were placeholder copy rather than your accounts.</>}
              metrics={['Network connections', 'Commission rates', 'Link rewriting', 'Attributed orders']}
            />
          )}
          {tab === 'payouts' && (
            <NotTracked
              title="Payouts"
              body={<>There is no earnings data and no payout provider connected,
                so there is no balance, schedule or history to show. Any figure
                here would be invented.</>}
              metrics={['Available balance', 'Pending earnings', 'Payout schedule', 'Payout history', 'Tax details']}
            />
          )}
          {tab === 'notifications' && (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
              {[
                ['New sale',           true],
                ['Outfit milestone',   true],
                ['Weekly performance digest', true],
                ['Trending in your tags',     false],
                ['Brand collab requests',     true],
              ].map(([label, defOn]) => (
                <div key={label} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '10px 0', borderBottom: '1px solid rgba(20,20,18,0.04)' }}>
                  <span style={{ fontSize: 13.5 }}>{label}</span>
                  <Toggle defaultOn={defOn}/>
                </div>
              ))}
            </div>
          )}
        </Card>
      </div>
    </div>
  );
}

function Toggle({ defaultOn }) {
  const [on, setOn] = useStateVB(defaultOn);
  return (
    <button onClick={() => setOn(!on)} style={{
      appearance: 'none', border: 'none', cursor: 'pointer',
      width: 36, height: 20, borderRadius: 999,
      background: on ? '#1a1a18' : 'rgba(20,20,18,0.18)',
      position: 'relative', transition: 'background .2s',
    }}>
      <span style={{
        position: 'absolute', top: 2, left: on ? 18 : 2, width: 16, height: 16,
        background: '#FAFAF7', borderRadius: '50%', transition: 'left .2s',
        boxShadow: '0 1px 3px rgba(20,20,18,0.2)',
      }}/>
    </button>
  );
}

Object.assign(window, { StudioAnalytics, StudioRevenue, StudioAudience, StudioDrafts, StudioMoodboards, StudioSettings, Toggle });
