// Main app — sidebar, topbar, dashboard strip, table, view switching.
const { useState: useS, useEffect: useE, useMemo: useM, useRef: useR, useCallback: useCB } = React;

// ── Brand mark ────────────────────────────────────────────
function BrandMark() {
  return (
    <img
      src="assets/logo.png"
      alt="OXIDE"
      width="26"
      height="26"
      style={{ display: "block", borderRadius: 6, flexShrink: 0 }}
    />
  );
}

// ── Sidebar ───────────────────────────────────────────────
function Sidebar({ active, onNav, collapsed, onToggle, user, onSignOut, creatorCount }) {
  const items = [
    { k: "dashboard", l: "Dashboard", icon: IconDashboard },
    { k: "creators", l: "Creators", icon: IconUsers, badge: String(creatorCount ?? 0) },
    { k: "pipeline", l: "Pipeline", icon: IconKanban },
    { k: "calendar", l: "Calendar", icon: IconCalendar },
    { k: "analytics", l: "Analytics", icon: IconChart },
  ];
  const items2 = [
    { k: "imports", l: "Import", icon: IconImport },
    { k: "settings", l: "Settings", icon: IconSettings },
  ];

  return (
    <aside className="sidebar">
      <div className="sidebar-brand">
        <BrandMark />
        <span className="brand-name">OXIDE<span style={{ color: "var(--text-tertiary)", fontWeight: 600 }}>: Influencers</span></span>
        <span className="brand-tag">BETA</span>
      </div>
      <div className="sidebar-nav">
        <div className="sidebar-section-label">Workspace</div>
        {items.map(it => {
          const I = it.icon;
          return (
            <div key={it.k} className={`nav-item ${active === it.k ? "active" : ""}`} onClick={() => onNav(it.k)}>
              <I />
              <span>{it.l}</span>
              {it.badge && <span className="nav-badge">{it.badge}</span>}
            </div>
          );
        })}
        <div className="sidebar-section-label">Other</div>
        {items2.map(it => {
          const I = it.icon;
          return (
            <div key={it.k} className={`nav-item ${active === it.k ? "active" : ""}`} onClick={() => onNav(it.k)}>
              <I />
              <span>{it.l}</span>
            </div>
          );
        })}
      </div>
      <div className="sidebar-footer">
        <div className="user-avatar" style={user?.color ? { background: `linear-gradient(135deg, ${user.color[0]}, ${user.color[1]})` } : {}}>{user?.initials || "?"}</div>
        <div className="user-meta">
          <div className="name">{user?.name || "Guest"}</div>
          <div className="role">{user?.email || "—"}</div>
        </div>
        <button className="btn ghost icon-only" data-tooltip="Sign out" onClick={onSignOut} style={{ marginLeft: "auto" }}><IconExternal size={14} /></button>
      </div>
    </aside>
  );
}

// ── Topbar ────────────────────────────────────────────────
function Topbar({ active, query, onQuery, onTheme, theme, onAdd, onSidebar, onSettings, onSync }) {
  const labels = {
    dashboard: "Dashboard",
    creators: "Creators",
    pipeline: "Deal pipeline",
    calendar: "Publication calendar",
    analytics: "Analytics",
    imports: "Import",
    settings: "Settings",
  };
  const cloudCfg = storage.cloud.getConfig();
  const cloudConfigured = !!cloudCfg.url;
  return (
    <header className="topbar">
      <button className="btn ghost icon-only" onClick={onSidebar} data-tooltip="Collapse"><IconMenuLeft size={16} /></button>
      <div className="crumbs">
        <span>Workspace</span>
        <span className="sep"><IconChevron size={11} /></span>
        <span className="current">{labels[active]}</span>
      </div>
      <div style={{ width: 12 }} />
      <div className="topbar-search">
        <IconSearch size={14} stroke="var(--text-tertiary)" />
        <input
          placeholder="Search creator, channel, contact…"
          value={query}
          onChange={(e) => onQuery(e.target.value)}
        />
        <span className="kbd">⌘K</span>
      </div>
      <div className="topbar-spacer" />
      <div className="topbar-actions">
        <SyncBadge cloudConfigured={cloudConfigured} onSync={onSync} onSettings={onSettings} />
        <button className="btn ghost icon-only" data-tooltip="Notifications"><IconBell size={16} /></button>
        <button className="btn ghost icon-only" onClick={onTheme} data-tooltip={theme === "dark" ? "Light theme" : "Dark theme"}>
          {theme === "dark" ? <IconSun size={16} /> : <IconMoon size={16} />}
        </button>
        <div style={{ width: 8 }} />
        <button className="btn primary" onClick={onAdd}>
          <IconPlus size={14} /> Creator
        </button>
      </div>
    </header>
  );
}

function SyncBadge({ cloudConfigured, onSync, onSettings }) {
  const [busy, setBusy] = useS(false);
  const [flash, setFlash] = useS(null); // 'ok' | 'err' | null
  const [, setTick] = useS(0); // force re-render to keep relTime fresh
  const cfg = storage.cloud.getConfig();

  // Keep "last pull" relative time live
  useE(() => {
    const t = setInterval(() => setTick(x => x + 1), 30000);
    return () => clearInterval(t);
  }, []);

  if (!cloudConfigured) {
    return (
      <button
        className="btn sm ghost"
        onClick={onSettings}
        data-tooltip="Set up cloud sync"
        style={{ color: "var(--text-tertiary)" }}
      >
        <IconRefresh size={13} /> No sync
      </button>
    );
  }

  const pull = async () => {
    setBusy(true);
    try {
      await storage.cloud.pull();
      setFlash("ok");
      setTimeout(() => location.reload(), 600);
    } catch (e) {
      setFlash("err");
      console.error(e);
      setBusy(false);
      setTimeout(() => setFlash(null), 2000);
    }
  };

  const lastLabel = cfg.lastPulledAt ? CRM.relTime(cfg.lastPulledAt) : "never";
  const stale = staleAge(cfg.lastPulledAt);

  return (
    <button
      className={`btn sm sync-badge ${flash === "ok" ? "primary" : ""} ${stale === "old" ? "stale" : ""}`}
      onClick={pull}
      disabled={busy}
      data-tooltip={cfg.lastPulledAt ? `Last sync: ${lastLabel} — click to refresh` : "Pull latest"}
    >
      <span className={`sync-dot ${stale}`} />
      <IconRefresh size={13} style={busy ? { animation: "spin 1s linear infinite" } : {}} />
      <span className="sync-label">
        {busy ? "Syncing…"
          : flash === "ok" ? "Synced"
          : flash === "err" ? "Failed"
          : (
              <>
                <span style={{ fontWeight: 600 }}>Sync</span>
                <span className="sync-when"> · {lastLabel}</span>
              </>
            )
        }
      </span>
    </button>
  );
}

// Categorize freshness: 'fresh' (<6h), 'aging' (6h–2d), 'old' (>2d), 'never' (no pull yet)
function staleAge(ts) {
  if (!ts) return "never";
  const hours = (Date.now() - ts) / 3600000;
  if (hours < 6) return "fresh";
  if (hours < 48) return "aging";
  return "old";
}

// ── Funnel + Budget strip ─────────────────────────────────
function DashboardStrip({ creators, statusFilter, onStatusToggle }) {
  const counts = {};
  Object.keys(STATUS_META).forEach(k => counts[k] = 0);
  let totalPlanned = 0, totalSpent = 0, totalForecastViews = 0, totalLiveViews = 0;
  creators.forEach(c => {
    counts[c.status]++;
    if (c.status !== "rejected" && c.status !== "lead") {
      totalPlanned += c.price;
    }
    if (["paid", "work", "pub"].includes(c.status)) {
      totalSpent += c.price;
    }
    if (c.status === "pub" && c.integration?.liveViews) {
      totalLiveViews += c.integration.liveViews;
      totalForecastViews += c.avgViews;
    }
  });
  const max = Math.max(...Object.values(counts));

  return (
    <div className="dashboard-strip">
      <div className="funnel-card">
        <div className="card-title-row">
          <div className="card-title">Pipeline by status</div>
          <span className="small muted">{creators.length} cards</span>
        </div>
        <div className="funnel">
          {Object.entries(STATUS_META).map(([k, m]) => (
            <button
              key={k}
              className={`funnel-step ${statusFilter.includes(k) ? "active" : ""}`}
              onClick={() => onStatusToggle(k)}
            >
              <div className="step-name">
                <span className="dot" style={{ background: m.color }} />
                {m.ru}
              </div>
              <div className="step-count">{counts[k]}</div>
              <div className="step-percent">{creators.length ? Math.round((counts[k] / creators.length) * 100) : 0}%</div>
            </button>
          ))}
        </div>
      </div>

      <div className="budget-card">
        <div className="card-title-row">
          <div className="card-title">Quarterly budget</div>
          <span className="small muted">Q2 2026</span>
        </div>
        <div className="budget-grid">
          <div className="budget-cell">
            <div className="label">Planned</div>
            <div className="value">{CRM.fmtMoney(totalPlanned)}</div>
            <div className="delta">{creators.filter(c => ["talks","paid","work","pub"].includes(c.status)).length} deals</div>
          </div>
          <div className="budget-cell">
            <div className="label">Spent</div>
            <div className="value">{CRM.fmtMoney(totalSpent)}</div>
            <div className="delta up">+{Math.round((totalSpent / totalPlanned) * 100) || 0}% of plan</div>
          </div>
          <div className="budget-cell">
            <div className="label">Views</div>
            <div className="value">{CRM.fmtNum(totalLiveViews)}</div>
            <div className={`delta ${totalLiveViews >= totalForecastViews ? "up" : "down"}`}>
              {totalForecastViews ? `${Math.round((totalLiveViews / totalForecastViews) * 100)}% of forecast` : "—"}
            </div>
          </div>
        </div>
        <div className="budget-bar">
          <span style={{ width: `${totalPlanned ? (totalSpent / totalPlanned) * 100 : 0}%`, background: "var(--accent-500)" }} />
          <span style={{ flex: 1, background: "var(--bg-hover)" }} />
        </div>
        <div className="budget-legend">
          <span><span className="legend-dot" style={{ background: "var(--accent-500)" }} />Spent</span>
          <span><span className="legend-dot" style={{ background: "var(--bg-hover)" }} />Remaining</span>
        </div>
      </div>
    </div>
  );
}

// ── Filter pill set ───────────────────────────────────────
function FilterChips({ statusFilter, onClear, platformFilter, onClearPlat }) {
  const chips = [];
  statusFilter.forEach(s => chips.push({ k: `s:${s}`, label: STATUS_META[s].ru, onX: () => onClear(s) }));
  platformFilter.forEach(p => chips.push({ k: `p:${p}`, label: PLATFORM_META[p].ru, onX: () => onClearPlat(p) }));
  if (chips.length === 0) return null;
  return (
    <>
      {chips.map(c => (
        <span className="filter-chip" key={c.k}>
          {c.label}
          <span className="x" onClick={c.onX}><IconClose size={10} /></span>
        </span>
      ))}
    </>
  );
}

// ── Toolbar (view switch + filters + sort) ────────────────
function Toolbar({ view, onView, filtersOpen, onFilters, sortKey, onSort, columnsOpen, onColumns,
                   statusFilter, setStatusFilter, platformFilter, setPlatformFilter,
                   priceRange, setPriceRange, managerFilter, setManagerFilter }) {
  const filterBtnRef = useR(null);
  const sortBtnRef = useR(null);
  const colsBtnRef = useR(null);

  const SORTS = [
    ["addedAt", "Date added", "desc"],
    ["price", "Integration price", "desc"],
    ["subs", "Subscribers", "desc"],
    ["avgViews", "Avg views", "desc"],
    ["cpv", "CPV", "asc"],
    ["lastContactAt", "Last contact", "desc"],
  ];

  return (
    <div className="toolbar">
      <div className="view-switch">
        <button className={view === "table" ? "active" : ""} onClick={() => onView("table")}>
          <IconColumns /> Table
        </button>
        <button className={view === "kanban" ? "active" : ""} onClick={() => onView("kanban")}>
          <IconKanban /> Kanban
        </button>
        <button className={view === "calendar" ? "active" : ""} onClick={() => onView("calendar")}>
          <IconCalendar /> Calendar
        </button>
      </div>

      <button ref={filterBtnRef} className="btn sm" onClick={() => onFilters(!filtersOpen)}>
        <IconFilter /> Filters {(statusFilter.length + platformFilter.length) ? `· ${statusFilter.length + platformFilter.length}` : ""}
        <IconChevronDown size={11} className="chev" />
      </button>
      {filtersOpen && (
        <Popover anchor={filterBtnRef.current} onClose={() => onFilters(false)}>
          <div className="pop-section">Platform</div>
          {Object.entries(PLATFORM_META).map(([k, m]) => (
            <div
              key={k}
              className={`pop-item ${platformFilter.includes(k) ? "selected" : ""}`}
              onClick={() => setPlatformFilter(platformFilter.includes(k) ? platformFilter.filter(x => x !== k) : [...platformFilter, k])}
            >
              <PlatformBadge platform={k} size={14} />
              {m.ru}
              <IconCheck size={14} className="check" />
            </div>
          ))}
          <div className="pop-divider" />
          <div className="pop-section">Manager</div>
          {CRM.MANAGERS.map(m => (
            <div
              key={m.name}
              className={`pop-item ${managerFilter.includes(m.name) ? "selected" : ""}`}
              onClick={() => setManagerFilter(managerFilter.includes(m.name) ? managerFilter.filter(x => x !== m.name) : [...managerFilter, m.name])}
            >
              <span className="avatar" style={{ width: 18, height: 18, borderRadius: "50%", background: `linear-gradient(135deg, ${m.color[0]}, ${m.color[1]})`, color: "#fff", fontSize: 9, fontWeight: 700, display: "grid", placeItems: "center" }}>{m.initials}</span>
              {m.name}
              <IconCheck size={14} className="check" />
            </div>
          ))}
          <div className="pop-divider" />
          <div className="pop-section">Price (K ₽)</div>
          <div style={{ padding: "6px 8px", display: "flex", gap: 6, alignItems: "center", fontSize: 12 }}>
            <input type="number" placeholder="from" style={{ width: 70, padding: "5px 8px", border: "1px solid var(--border-default)", borderRadius: 6, background: "var(--bg-app)" }} value={priceRange[0]} onChange={(e) => setPriceRange([+e.target.value || 0, priceRange[1]])} />
            <span>—</span>
            <input type="number" placeholder="to" style={{ width: 70, padding: "5px 8px", border: "1px solid var(--border-default)", borderRadius: 6, background: "var(--bg-app)" }} value={priceRange[1]} onChange={(e) => setPriceRange([priceRange[0], +e.target.value || 0])} />
            <span className="small muted">K ₽</span>
          </div>
          <div className="pop-divider" />
          <div className="pop-item" style={{ color: "var(--text-secondary)" }} onClick={() => { setStatusFilter([]); setPlatformFilter([]); setManagerFilter([]); setPriceRange([0, 0]); }}>
            <IconClose size={12} /> Clear all
          </div>
        </Popover>
      )}

      <button ref={sortBtnRef} className="btn sm" onClick={() => onSort.toggle(!onSort.open)}>
        <IconSort /> Sort: <b style={{ marginLeft: 2 }}>{SORTS.find(([k]) => k === sortKey)?.[1]}</b>
        <IconChevronDown size={11} className="chev" />
      </button>
      {onSort.open && (
        <Popover anchor={sortBtnRef.current} onClose={() => onSort.toggle(false)}>
          {SORTS.map(([k, l]) => (
            <div
              key={k}
              className={`pop-item ${sortKey === k ? "selected" : ""}`}
              onClick={() => { onSort.set(k); onSort.toggle(false); }}
            >
              {l}
              <IconCheck size={14} className="check" />
            </div>
          ))}
        </Popover>
      )}

      <FilterChips
        statusFilter={statusFilter}
        onClear={(s) => setStatusFilter(statusFilter.filter(x => x !== s))}
        platformFilter={platformFilter}
        onClearPlat={(p) => setPlatformFilter(platformFilter.filter(x => x !== p))}
      />

      <div className="spacer" />

      <button className="btn sm ghost"><IconUpload /> Import CSV</button>
    </div>
  );
}

// ── Table ─────────────────────────────────────────────────
function CreatorTable({ creators, activeId, onRowClick, sortKey, sortDir, onSortChange, selected, onToggleSelect, onToggleAll }) {
  const allSelected = creators.length > 0 && creators.every(c => selected.has(c.id));
  const COLS = [
    { k: "_sel" },
    { k: "status", l: "Status", sortable: false },
    { k: "creator", l: "Creator", className: "cell-creator" },
    { k: "subs", l: "Subs", sortable: true, num: true },
    { k: "avgViews", l: "Avg views", sortable: true, num: true },
    { k: "trend", l: "Trend" },
    { k: "cpv", l: "CPV", sortable: true, num: true },
    { k: "price", l: "Price", sortable: true, num: true },
    { k: "contacts", l: "Contacts" },
    { k: "manager", l: "Manager" },
    { k: "link", l: "Channel" },
    { k: "last", l: "Contact" },
  ];

  return (
    <div className="table-wrap">
      <div className="table-scroll">
        <table className="creators">
          <thead>
            <tr>
              <th className="cell-checkbox">
                <input type="checkbox" className="cb" checked={allSelected} onChange={onToggleAll} />
              </th>
              {COLS.slice(1).map(c => (
                <th
                  key={c.k}
                  className={`${sortKey === c.k ? "sorted" : ""}`}
                  onClick={() => c.sortable && onSortChange(c.k)}
                  style={c.num ? { textAlign: "right" } : {}}
                >
                  {c.l}
                  {c.sortable && (
                    <span className="sort-arrow">
                      {sortKey === c.k ? (sortDir === "asc" ? "▲" : "▼") : "▼"}
                    </span>
                  )}
                </th>
              ))}
            </tr>
          </thead>
          <tbody>
            {creators.map(c => (
              <tr
                key={c.id}
                className={`${activeId === c.id ? "active" : ""} ${selected.has(c.id) ? "selected" : ""}`}
                onClick={() => onRowClick(c)}
              >
                <td className="cell-checkbox" onClick={(e) => { e.stopPropagation(); onToggleSelect(c.id); }}>
                  <input type="checkbox" className="cb" checked={selected.has(c.id)} readOnly />
                </td>
                <td><StatusPill status={c.status} /></td>
                <td className="cell-creator">
                  <div className="creator-cell">
                    <Avatar creator={c} size={30} />
                    <div className="info">
                      <div className="name">{c.name}</div>
                      <div className="handle">{c.handle}</div>
                    </div>
                  </div>
                </td>
                <td className="num" style={{ textAlign: "right" }}>{CRM.fmtNum(c.subs)}</td>
                <td className="num" style={{ textAlign: "right" }}>{CRM.fmtNum(c.avgViews)}</td>
                <td><Sparkline data={c.spark} width={56} height={18} /></td>
                <td className="cell-cpv num" style={{ textAlign: "right" }}>{c.cpv} ₽</td>
                <td className="cell-price num" style={{ textAlign: "right" }}>{CRM.fmtMoney(c.price)}</td>
                <td><ContactsStack contacts={c.contacts} /></td>
                <td>
                  <div className="row gap-8">
                    <div className="avatar" style={{ width: 22, height: 22, borderRadius: "50%", background: `linear-gradient(135deg, ${c.manager.color[0]}, ${c.manager.color[1]})`, color: "#fff", fontSize: 10, fontWeight: 700, display: "grid", placeItems: "center" }}>{c.manager.initials}</div>
                    <span className="small">{c.manager.name.split(" ")[0]}</span>
                  </div>
                </td>
                <td className="cell-link">
                  <a href={c.channelUrl} target="_blank" rel="noopener" onClick={(e) => e.stopPropagation()}>
                    {c.handle.replace("@", "")}<IconExternal size={11} />
                  </a>
                </td>
                <td className="small muted">{CRM.relTime(c.lastContactAt)}</td>
              </tr>
            ))}
            {creators.length === 0 && (
              <tr><td colSpan={12}><div className="empty"><div>🔍</div><div>No results</div></div></td></tr>
            )}
          </tbody>
        </table>
      </div>
      <TablePagination />
    </div>
  );
}

function TablePagination() {
  return (
    <div className="pagination">
      <div className="small">Showing <b>1–50</b> of <b>500</b></div>
      <div className="pages">
        <button disabled><IconChevronLeft size={12} /></button>
        <button className="active">1</button>
        <button>2</button>
        <button>3</button>
        <button>4</button>
        <button>5</button>
        <button>…</button>
        <button>10</button>
        <button><IconChevron size={12} /></button>
      </div>
    </div>
  );
}

// ── Add Creator modal ─────────────────────────────────────
function AddCreatorModal({ onClose, onAdd }) {
  const [form, setForm] = useS({
    name: "", handle: "", platform: "youtube", price: "", status: "lead", manager: "Maria Leschenko",
  });
  const submit = (e) => {
    e.preventDefault();
    if (!form.name || !form.handle) return;
    onAdd(form);
    onClose();
  };
  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        <h2>New creator</h2>
        <div className="modal-sub">Fill in the basics. Metrics will pull in automatically once you link the channel.</div>
        <form onSubmit={submit}>
          <div className="form-row">
            <div className="field">
              <label>Name</label>
              <input value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} placeholder="Anna Kravtsova" autoFocus />
            </div>
            <div className="field">
              <label>Handle</label>
              <input value={form.handle} onChange={(e) => setForm({ ...form, handle: e.target.value })} placeholder="@anya_creates" />
            </div>
          </div>
          <div className="form-row">
            <div className="field">
              <label>Platform</label>
              <select value={form.platform} onChange={(e) => setForm({ ...form, platform: e.target.value })}>
                {Object.entries(PLATFORM_META).map(([k, m]) => <option key={k} value={k}>{m.ru}</option>)}
              </select>
            </div>
            <div className="field">
              <label>Status</label>
              <select value={form.status} onChange={(e) => setForm({ ...form, status: e.target.value })}>
                {Object.entries(STATUS_META).map(([k, m]) => <option key={k} value={k}>{m.ru}</option>)}
              </select>
            </div>
          </div>
          <div className="form-row">
            <div className="field">
              <label>Integration price, ₽</label>
              <input type="number" value={form.price} onChange={(e) => setForm({ ...form, price: e.target.value })} placeholder="350000" />
            </div>
            <div className="field">
              <label>Manager</label>
              <select value={form.manager} onChange={(e) => setForm({ ...form, manager: e.target.value })}>
                {CRM.MANAGERS.map(m => <option key={m.name}>{m.name}</option>)}
              </select>
            </div>
          </div>
          <div className="modal-actions">
            <button type="button" className="btn" onClick={onClose}>Cancel</button>
            <button type="submit" className="btn primary"><IconPlus size={14} /> Add</button>
          </div>
        </form>
      </div>
    </div>
  );
}

// ── Bulk-actions bar (when something is selected) ─────────
function BulkBar({ count, onClear }) {
  if (count === 0) return null;
  return (
    <div style={{
      position: "fixed", left: "50%", bottom: 20, transform: "translateX(-50%)",
      background: "var(--text-primary)", color: "var(--bg-surface)",
      padding: "8px 14px", borderRadius: 999, display: "flex", alignItems: "center", gap: 12,
      boxShadow: "var(--shadow-lg)", zIndex: 30, fontSize: 13, fontWeight: 600,
    }}>
      <span>Selected: {count}</span>
      <span style={{ width: 1, height: 14, background: "rgba(255,255,255,0.2)" }} />
      <button className="btn sm" style={{ background: "transparent", color: "inherit", border: "1px solid rgba(255,255,255,0.2)" }}>
        <IconMail size={11} /> Email
      </button>
      <button className="btn sm" style={{ background: "transparent", color: "inherit", border: "1px solid rgba(255,255,255,0.2)" }}>
        Change status
      </button>
      <button className="btn sm" style={{ background: "transparent", color: "inherit", border: "1px solid rgba(255,255,255,0.2)" }}>
        <IconTrash size={11} />
      </button>
      <button className="btn ghost sm" style={{ color: "inherit" }} onClick={onClear}>
        <IconClose size={12} />
      </button>
    </div>
  );
}

// ── Root App ──────────────────────────────────────────────
function App() {
  const [loading, setLoading] = useS(true);
  const [theme, setTheme] = useS(() => localStorage.getItem("crm-theme") || "light");
  const [user, setUser] = useS(() => storage.auth.currentUser());
  const [active, setActive] = useS("creators");
  const [sidebarCollapsed, setSidebarCollapsed] = useS(false);
  const [view, setView] = useS("table");
  const [query, setQuery] = useS("");
  const [creators, setCreators] = useS(() => storage.creators.list());
  const [activeCreator, setActiveCreator] = useS(null);
  const [showAdd, setShowAdd] = useS(false);

  const [statusFilter, setStatusFilter] = useS([]);
  const [platformFilter, setPlatformFilter] = useS([]);
  const [managerFilter, setManagerFilter] = useS([]);
  const [priceRange, setPriceRange] = useS([0, 0]);
  const [sortKey, setSortKey] = useS("addedAt");
  const [sortDir, setSortDir] = useS("desc");
  const [filtersOpen, setFiltersOpen] = useS(false);
  const [sortOpen, setSortOpen] = useS(false);
  const [columnsOpen, setColumnsOpen] = useS(false);
  const [selected, setSelected] = useS(new Set());

  // Theme persistence
  useE(() => {
    document.documentElement.dataset.theme = theme;
    localStorage.setItem("crm-theme", theme);
  }, [theme]);

  // Splash screen on launch — fixed 2.5s
  useE(() => {
    const t = setTimeout(() => setLoading(false), 2500);
    return () => clearTimeout(t);
  }, []);

  // Session activity tracking — heartbeat every 60s + on user actions.
  // If inactive for 48h, force re-login.
  useE(() => {
    if (!user) return;
    storage.auth.touchActivity();
    const heartbeat = setInterval(() => {
      if (storage.auth.isSessionExpired()) {
        storage.auth.signOut();
        setUser(null);
        return;
      }
      storage.auth.touchActivity();
    }, 60000);
    const onActivity = () => storage.auth.touchActivity();
    window.addEventListener("click", onActivity);
    window.addEventListener("keydown", onActivity);
    // Check on focus/visibility — most likely path to a stale session
    const onFocus = () => {
      if (storage.auth.isSessionExpired()) {
        storage.auth.signOut();
        setUser(null);
      } else {
        storage.auth.touchActivity();
      }
    };
    window.addEventListener("focus", onFocus);
    document.addEventListener("visibilitychange", () => {
      if (document.visibilityState === "visible") onFocus();
    });
    return () => {
      clearInterval(heartbeat);
      window.removeEventListener("click", onActivity);
      window.removeEventListener("keydown", onActivity);
      window.removeEventListener("focus", onFocus);
    };
  }, [user]);

  // Browser notifications — check at startup, when creator list changes, and hourly
  useE(() => {
    if (!user) return;
    notifications.check(creators);
    const t = setInterval(() => notifications.check(creators), 3600000);
    return () => clearInterval(t);
  }, [user, creators]);

  // ── Auto-pull on launch ────────────────────────────────
  // If cloud is configured, silently pull on app start to get the freshest snapshot.
  // We avoid the page reload (it would be jarring on every visit) — instead refresh
  // the in-memory creators list from storage after a successful pull.
  const [autoSyncState, setAutoSyncState] = useS("idle"); // idle | running | done | error
  const [staleBanner, setStaleBanner] = useS(false);

  useE(() => {
    if (!user) return; // wait until signed in
    const cfg = storage.cloud.getConfig();
    if (!cfg.url) {
      setAutoSyncState("idle");
      checkStale();
      return;
    }
    setAutoSyncState("running");
    storage.cloud.pull()
      .then(() => {
        setCreators(storage.creators.list());
        setAutoSyncState("done");
        // Small toast that fades out
        setTimeout(() => setAutoSyncState("idle"), 2400);
        checkStale();
      })
      .catch((e) => {
        console.warn("Auto-pull failed:", e.message);
        setAutoSyncState("error");
        setTimeout(() => setAutoSyncState("idle"), 3500);
        checkStale();
      });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [user]);

  function checkStale() {
    const cfg = storage.cloud.getConfig();
    if (!cfg.url) return; // nothing to be stale about
    const last = cfg.lastPulledAt;
    if (!last) {
      setStaleBanner(true);
      return;
    }
    const days = (Date.now() - last) / 86400000;
    if (days >= 2) setStaleBanner(true);
  }

  // Filters chained
  const filtered = useM(() => {
    let arr = creators;
    if (statusFilter.length) arr = arr.filter(c => statusFilter.includes(c.status));
    if (platformFilter.length) arr = arr.filter(c => platformFilter.includes(c.platform));
    if (managerFilter.length) arr = arr.filter(c => managerFilter.includes(c.manager.name));
    if (priceRange[0]) arr = arr.filter(c => c.price >= priceRange[0] * 1000);
    if (priceRange[1]) arr = arr.filter(c => c.price <= priceRange[1] * 1000);
    if (query.trim()) {
      const q = query.toLowerCase();
      arr = arr.filter(c =>
        c.name.toLowerCase().includes(q) ||
        c.handle.toLowerCase().includes(q) ||
        c.niche.toLowerCase().includes(q) ||
        c.contacts.some(x => x.name.toLowerCase().includes(q))
      );
    }
    // Sort
    arr = [...arr].sort((a, b) => {
      const av = a[sortKey], bv = b[sortKey];
      if (av == null) return 1;
      if (bv == null) return -1;
      const cmp = typeof av === "string" ? av.localeCompare(bv) : av - bv;
      return sortDir === "asc" ? cmp : -cmp;
    });
    return arr;
  }, [creators, statusFilter, platformFilter, managerFilter, priceRange, query, sortKey, sortDir]);

  const visible = view === "table" ? filtered.slice(0, 50) : filtered;

  // Handlers — all mutations go through storage so they persist + sync across tabs
  const updateCreator = useCB((id, patch) => {
    const updated = storage.creators.update(id, patch);
    setCreators(storage.creators.list());
    if (activeCreator?.id === id) setActiveCreator(updated);
  }, [activeCreator]);

  const onRowClick = (c) => setActiveCreator(c);
  const onSortChange = (k) => {
    if (k === sortKey) setSortDir(d => d === "asc" ? "desc" : "asc");
    else { setSortKey(k); setSortDir("desc"); }
  };
  const toggleStatus = (s) => {
    setStatusFilter(prev => prev.includes(s) ? prev.filter(x => x !== s) : [...prev, s]);
  };
  const toggleSelect = (id) => {
    setSelected(prev => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id); else next.add(id);
      return next;
    });
  };
  const toggleAll = () => {
    setSelected(prev => {
      if (visible.every(c => prev.has(c.id))) {
        const next = new Set(prev);
        visible.forEach(c => next.delete(c.id));
        return next;
      }
      const next = new Set(prev);
      visible.forEach(c => next.add(c.id));
      return next;
    });
  };
  const addCreator = (form) => {
    const id = "cr_new_" + Date.now();
    const newCreator = {
      id,
      name: form.name,
      realName: form.name,
      handle: form.handle.startsWith("@") ? form.handle : "@" + form.handle,
      avatarColor: ["#6f4cff", "#ff6b9d"],
      initials: form.name.split(" ").map(x => x[0]).slice(0, 2).join("").toUpperCase(),
      platform: form.platform,
      channelUrl: `https://${form.platform}.com/${form.handle.replace("@","")}`,
      niche: "Not specified",
      country: "RU", city: "—",
      subs: 0, avgViews: 0, cpv: 0, cpm: 0,
      price: +form.price || 0,
      currency: "RUB",
      status: form.status,
      contacts: [],
      manager: CRM.MANAGERS.find(m => m.name === form.manager),
      geo: { RU: 100 }, age: { "18–24": 100 }, gender: { male: 50, female: 50 },
      spark: [0,0,0,0,0,0,0,0],
      integration: null,
      activity: [{ when: new Date(), what: "Card created" }],
      addedAt: Date.now(),
      lastContactAt: Date.now(),
      notes: "",
    };
    storage.creators.create(newCreator);
    setCreators(storage.creators.list());
  };

  // Auth gate — show sign-in screen first
  if (loading) {
    return <LoadingScreen />;
  }
  if (!user) {
    return <SignInScreen onSignIn={(u) => { storage.auth.signIn(u); setUser(u); }} theme={theme} onTheme={() => setTheme(t => t === "dark" ? "light" : "dark")} />;
  }

  return (
    <div className={`app ${sidebarCollapsed ? "sidebar-collapsed" : ""}`}>
      <Sidebar active={active} onNav={setActive} collapsed={sidebarCollapsed} onToggle={() => setSidebarCollapsed(c => !c)} user={user} onSignOut={() => { storage.auth.signOut(); setUser(null); }} creatorCount={creators.length} />
      <Topbar
        active={active}
        query={query}
        onQuery={setQuery}
        theme={theme}
        onTheme={() => setTheme(t => t === "dark" ? "light" : "dark")}
        onAdd={() => setShowAdd(true)}
        onSidebar={() => setSidebarCollapsed(c => !c)}
        onSettings={() => setActive("settings")}
      />
      <main className="main">
        <div className="page">
          <div className="page-header">
            <div>
              <h1 className="page-title">
                {active === "creators" && <>Creators <span className="count">{filtered.length} of {creators.length}</span></>}
                {active === "dashboard" && <>Dashboard</>}
                {active === "pipeline" && <>Deal pipeline</>}
                {active === "calendar" && <>Publication calendar</>}
                {active === "analytics" && <>Analytics</>}
                {active === "imports" && <>Import</>}
                {active === "settings" && <>Settings</>}
              </h1>
              <div className="page-subtitle">
                {active === "creators" && "Partnership base: statuses, budget, contacts, channel metrics"}
                {active === "dashboard" && "Quarterly overview of creator collaborations"}
                {active === "pipeline" && "Status kanban — drag cards between columns"}
                {active === "calendar" && "Scheduled and published integrations"}
              </div>
            </div>
          </div>

          {/* Dashboard strip shown on creators + dashboard + pipeline */}
          {(active === "creators" || active === "dashboard" || active === "pipeline") && (
            <DashboardStrip creators={creators} statusFilter={statusFilter} onStatusToggle={toggleStatus} />
          )}

          {/* Creators page */}
          {(active === "creators" || active === "pipeline") && (
            <>
              <Toolbar
                view={active === "pipeline" ? "kanban" : view}
                onView={setView}
                filtersOpen={filtersOpen}
                onFilters={setFiltersOpen}
                sortKey={sortKey}
                onSort={{ open: sortOpen, toggle: setSortOpen, set: setSortKey }}
                columnsOpen={columnsOpen}
                onColumns={setColumnsOpen}
                statusFilter={statusFilter}
                setStatusFilter={setStatusFilter}
                platformFilter={platformFilter}
                setPlatformFilter={setPlatformFilter}
                priceRange={priceRange}
                setPriceRange={setPriceRange}
                managerFilter={managerFilter}
                setManagerFilter={setManagerFilter}
              />

              {(active === "pipeline" || view === "kanban") && (
                <KanbanBoard
                  creators={filtered}
                  onCardClick={(c) => setActiveCreator(c)}
                  onStatusChange={(id, status) => updateCreator(id, { status })}
                />
              )}
              {active !== "pipeline" && view === "table" && (
                <CreatorTable
                  creators={visible}
                  activeId={activeCreator?.id}
                  onRowClick={onRowClick}
                  sortKey={sortKey}
                  sortDir={sortDir}
                  onSortChange={onSortChange}
                  selected={selected}
                  onToggleSelect={toggleSelect}
                  onToggleAll={toggleAll}
                />
              )}
              {active !== "pipeline" && view === "calendar" && (
                <CalendarView creators={filtered} onEventClick={setActiveCreator} />
              )}
            </>
          )}

          {/* Dedicated Calendar page */}
          {active === "calendar" && (
            <CalendarView creators={filtered} onEventClick={setActiveCreator} />
          )}

          {/* Analytics placeholder */}
          {active === "analytics" && <AnalyticsPlaceholder creators={creators} />}
          {active === "dashboard" && <DashboardExtras creators={creators} onRowClick={setActiveCreator} />}
          {active === "imports" && <ImportsPlaceholder />}
          {active === "settings" && <SettingsPlaceholder theme={theme} onTheme={setTheme} />}
        </div>
      </main>

      {activeCreator && (
        <CreatorDrawer
          creator={activeCreator}
          onClose={() => setActiveCreator(null)}
          onChange={(patch) => updateCreator(activeCreator.id, patch)}
        />
      )}

      {showAdd && <AddCreatorModal onClose={() => setShowAdd(false)} onAdd={addCreator} />}

      <BulkBar count={selected.size} onClear={() => setSelected(new Set())} />

      {staleBanner && (
        <StaleBanner
          lastPulledAt={storage.cloud.getConfig().lastPulledAt}
          onDismiss={() => setStaleBanner(false)}
          onSync={async () => {
            try {
              await storage.cloud.pull();
              setCreators(storage.creators.list());
              setStaleBanner(false);
              setAutoSyncState("done");
              setTimeout(() => setAutoSyncState("idle"), 2400);
            } catch (e) {
              alert("Sync failed: " + e.message);
            }
          }}
        />
      )}

      {autoSyncState !== "idle" && <AutoSyncToast state={autoSyncState} />}
    </div>
  );
}

// ── Stale-data banner ─────────────────────────────────────
function StaleBanner({ lastPulledAt, onDismiss, onSync }) {
  const [busy, setBusy] = useS(false);
  const days = lastPulledAt ? Math.floor((Date.now() - lastPulledAt) / 86400000) : null;
  const handle = async () => {
    setBusy(true);
    try { await onSync(); } finally { setBusy(false); }
  };
  return (
    <div className="stale-banner">
      <div className="stale-banner-icon"><IconClock size={18} /></div>
      <div style={{ flex: 1 }}>
        <div className="stale-banner-title">Your data may be out of date</div>
        <div className="stale-banner-sub">
          {days != null
            ? `Last synced ${days} day${days === 1 ? "" : "s"} ago — pull the latest to see what the team has added.`
            : "You haven't synced yet — pull the shared snapshot to start with current data."}
        </div>
      </div>
      <button className="btn primary" onClick={handle} disabled={busy}>
        <IconRefresh size={13} style={busy ? { animation: "spin 1s linear infinite" } : {}} />
        {busy ? "Syncing…" : "Sync now"}
      </button>
      <button className="btn ghost icon-only" onClick={onDismiss} data-tooltip="Dismiss">
        <IconClose size={14} />
      </button>
    </div>
  );
}

// ── Small toast for background auto-sync ──────────────────
function AutoSyncToast({ state }) {
  return (
    <div className={`auto-sync-toast ${state}`}>
      {state === "running" && <><IconRefresh size={13} style={{ animation: "spin 1s linear infinite" }} /> Syncing latest…</>}
      {state === "done"    && <><IconCheck size={13} /> Synced with cloud</>}
      {state === "error"   && <><IconClose size={13} /> Sync failed — using local data</>}
    </div>
  );
}

// ── Small inner pages ─────────────────────────────────────
function AnalyticsPlaceholder({ creators }) {
  // Aggregate platform-level numbers
  const byPlat = {};
  creators.forEach(c => {
    if (!byPlat[c.platform]) byPlat[c.platform] = { count: 0, price: 0, views: 0, cpv: [] };
    byPlat[c.platform].count++;
    byPlat[c.platform].price += c.price;
    byPlat[c.platform].views += c.avgViews;
    byPlat[c.platform].cpv.push(c.cpv);
  });

  return (
    <div>
      <div className="section-block">
        <div className="section-block-title">
          <span className="icon"><IconChart size={14} /></span>
          Platform summary
        </div>
        <table className="creators" style={{ width: "100%" }}>
          <thead>
            <tr>
              <th>Platform</th>
              <th style={{ textAlign: "right" }}>Creators</th>
              <th style={{ textAlign: "right" }}>Forecast views</th>
              <th style={{ textAlign: "right" }}>Avg CPV</th>
              <th style={{ textAlign: "right" }}>Total deals</th>
            </tr>
          </thead>
          <tbody>
            {Object.entries(byPlat).map(([p, d]) => (
              <tr key={p}>
                <td>
                  <div className="row gap-8">
                    <PlatformBadge platform={p} />
                    {PLATFORM_META[p].ru}
                  </div>
                </td>
                <td className="num" style={{ textAlign: "right" }}>{d.count}</td>
                <td className="num" style={{ textAlign: "right" }}>{CRM.fmtNum(d.views)}</td>
                <td className="num" style={{ textAlign: "right" }}>{(d.cpv.reduce((a,b)=>a+b,0) / d.cpv.length).toFixed(2)} ₽</td>
                <td className="num" style={{ textAlign: "right" }}>{CRM.fmtMoney(d.price)}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function DashboardExtras({ creators, onRowClick }) {
  // Top performing recently published
  const top = creators
    .filter(c => c.integration?.liveViews)
    .sort((a, b) => b.integration.liveViews - a.integration.liveViews)
    .slice(0, 8);
  return (
    <div>
      <div className="section-block">
        <div className="section-block-title">
          <span className="icon"><IconLightning size={14} /></span>
          Top performing publications
          <span className="meta">last 30 days</span>
        </div>
        {top.map(c => (
          <div key={c.id} className="contact-row" style={{ cursor: "pointer" }} onClick={() => onRowClick(c)}>
            <Avatar creator={c} size={32} />
            <div className="info">
              <div className="name">{c.name}</div>
              <div className="channel">{c.handle} · {PLATFORM_META[c.platform].ru}</div>
            </div>
            <div className="num bold" style={{ fontSize: 16 }}>{CRM.fmtNum(c.integration.liveViews)}</div>
            <div className="small muted" style={{ width: 80, textAlign: "right" }}>
              {Math.round((c.integration.liveViews / c.avgViews) * 100)}% of forecast
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

function ImportsPlaceholder() {
  return (
    <div className="section-block">
      <div className="section-block-title">
        <span className="icon"><IconUpload size={14} /></span>
        Import creators from spreadsheet
      </div>
      <div className="empty">
        <div style={{ fontSize: 36 }}>📥</div>
        <div style={{ marginTop: 8 }}>Drag a CSV/XLSX here or paste a table</div>
        <div className="small" style={{ marginTop: 4 }}>Supported columns: name, handle, platform, price, status, contacts</div>
        <button className="btn primary" style={{ marginTop: 14 }}>
          <IconUpload size={14} /> Upload file
        </button>
      </div>
    </div>
  );
}

function SettingsPlaceholder({ theme, onTheme }) {
  return (
    <div>
      <div className="section-block">
        <div className="section-block-title">
          <span className="icon"><IconSettings size={14} /></span>
          Appearance
        </div>
        <div className="between">
          <div>
            <div style={{ fontWeight: 600 }}>Dark theme</div>
            <div className="small muted">For night work. Saved in your browser.</div>
          </div>
          <div className="view-switch">
            <button className={theme === "light" ? "active" : ""} onClick={() => onTheme("light")}><IconSun /> Light</button>
            <button className={theme === "dark" ? "active" : ""} onClick={() => onTheme("dark")}><IconMoon /> Dark</button>
          </div>
        </div>
      </div>
      <div className="section-block">
        <div className="section-block-title">
          <span className="icon"><IconLink size={14} /></span>
          Platform API keys
          <span className="meta">for auto-syncing view counts</span>
        </div>
        {Object.entries(PLATFORM_META).map(([k, m]) => (
          <div key={k} className="contact-row">
            <PlatformBadge platform={k} size={32} />
            <div className="info">
              <div className="name">{m.ru} API</div>
              <div className="channel">Not connected</div>
            </div>
            <button className="btn sm">Connect</button>
          </div>
        ))}
      </div>

      <DataSection />
      <CloudSyncSection />
      <NotificationsSection />
    </div>
  );
}

// ── Notifications section (Settings) ──────────────────────
function NotificationsSection() {
  const [perm, setPerm] = useS(() => notifications.permission());
  const [status, setStatus] = useS(null);

  const enable = async () => {
    const result = await notifications.requestIfNeeded();
    setPerm(result);
    if (result === "granted") {
      setStatus({ type: "ok", msg: "Notifications enabled. You'll get alerts for scheduled events and new creators." });
      notifications.fire("OXIDE notifications are on", "You'll see alerts like this for shoots, publications, and new creators.", "test");
    } else if (result === "denied") {
      setStatus({ type: "error", msg: "Permission denied. You can enable it in your browser's site settings." });
    } else if (result === "unsupported") {
      setStatus({ type: "error", msg: "This browser doesn't support notifications." });
    }
  };

  const test = () => {
    notifications.fire("🎬 Test notification", "If you see this, everything is wired up correctly.", "test-" + Date.now());
  };

  const supported = notifications.isSupported();
  const pillColor =
    perm === "granted" ? "var(--success)" :
    perm === "denied" ? "var(--danger)" :
    "var(--warning)";
  const pillText =
    perm === "granted" ? "enabled" :
    perm === "denied" ? "blocked in browser" :
    perm === "unsupported" ? "not supported" :
    "not asked yet";

  return (
    <div className="section-block">
      <div className="section-block-title">
        <span className="icon"><IconBell size={14} /></span>
        Browser notifications
        <span className="meta" style={{ color: pillColor }}>● {pillText}</span>
      </div>

      <div className="small muted" style={{ marginBottom: 12, lineHeight: 1.7 }}>
        Desktop alerts for important moments. Fired once per event, per day:
      </div>
      <ul style={{ margin: "0 0 12px", paddingLeft: 20, fontSize: 13, color: "var(--text-secondary)", lineHeight: 1.8 }}>
        <li>🎬 <b>Shoot starts today</b> — when today matches a card's scheduled shoot date</li>
        <li>📢 <b>Publication today</b> — when today matches a card's scheduled publication date</li>
        <li>✨ <b>New creator added</b> — when someone on the team adds a creator to the shared base</li>
      </ul>

      <div className="row gap-8" style={{ flexWrap: "wrap" }}>
        {perm === "default" && (
          <button className="btn primary" onClick={enable} disabled={!supported}>
            <IconBell size={13} /> Enable notifications
          </button>
        )}
        {perm === "granted" && (
          <button className="btn" onClick={test}>
            <IconBell size={13} /> Send test notification
          </button>
        )}
        {perm === "denied" && (
          <div className="small muted" style={{ lineHeight: 1.6 }}>
            Notifications are blocked. To unblock: click the lock icon next to the address bar → Site settings → Notifications → Allow.
          </div>
        )}
        {!supported && (
          <div className="small muted">
            Notifications API not available in this browser.
          </div>
        )}
      </div>

      {status && (
        <div className="small" style={{
          marginTop: 12, padding: "8px 12px", borderRadius: 8,
          background: status.type === "ok" ? "rgba(34, 197, 94, 0.10)" : "rgba(210, 63, 63, 0.10)",
          color: status.type === "ok" ? "var(--success)" : "var(--danger)",
          border: `1px solid ${status.type === "ok" ? "rgba(34, 197, 94, 0.30)" : "rgba(210, 63, 63, 0.30)"}`,
          display: "flex", alignItems: "center", gap: 6, fontWeight: 600,
        }}>
          {status.type === "ok" ? <IconCheck size={13} /> : <IconClose size={13} />}
          {status.msg}
        </div>
      )}
    </div>
  );
}

// ── Cloud sync (shared JSON URL + auto-push to Gist) ──────
function CloudSyncSection() {
  const [config, setConfig] = useS(() => storage.cloud.getConfig());
  const [url, setUrl] = useS(config.url);
  const [token, setToken] = useS(() => storage.cloud.getToken());
  const [tokenVisible, setTokenVisible] = useS(false);
  const [busy, setBusy] = useS(false);
  const [status, setStatus] = useS(null); // { type: "ok"|"error", msg }
  const [showHelp, setShowHelp] = useS(false);
  const [pushState, setPushState] = useS(() => storage.cloud.getPushState());

  const refresh = () => {
    setConfig(storage.cloud.getConfig());
    setPushState(storage.cloud.getPushState());
  };

  // Re-render when storage notifies (e.g. auto-push state changes)
  useE(() => {
    const unsub = storage.subscribe(refresh);
    return unsub;
  }, []);

  const saveUrl = () => {
    const trimmed = url.trim();
    storage.cloud.setConfig({ url: trimmed });
    setStatus({ type: "ok", msg: trimmed ? "URL saved." : "URL cleared." });
    refresh();
  };

  const saveToken = () => {
    const trimmed = token.trim();
    storage.cloud.setToken(trimmed);
    setStatus({ type: "ok", msg: trimmed ? "Token saved — auto-push enabled." : "Token cleared — auto-push disabled." });
    refresh();
  };

  const testPush = async () => {
    setBusy(true);
    setStatus(null);
    try {
      await storage.cloud.push();
      setStatus({ type: "ok", msg: "Test push succeeded. Auto-push is working." });
      refresh();
    } catch (e) {
      setStatus({ type: "error", msg: e.message });
    } finally {
      setBusy(false);
    }
  };

  const onPull = async () => {
    if (!url.trim()) { setStatus({ type: "error", msg: "Enter a URL first." }); return; }
    setBusy(true);
    setStatus(null);
    try {
      const { count, snapshotAt } = await storage.cloud.pull();
      const when = snapshotAt ? CRM.relTime(snapshotAt) : "unknown time";
      setStatus({ type: "ok", msg: `Pulled ${count} creators (snapshot from ${when}). Reloading…` });
      setTimeout(() => location.reload(), 900);
    } catch (e) {
      setStatus({ type: "error", msg: e.message });
      setBusy(false);
    }
  };

  const toggleAutoPush = () => {
    const next = !config.autoPush;
    storage.cloud.setConfig({ autoPush: next });
    refresh();
    setStatus({ type: "ok", msg: next ? "Auto-push enabled." : "Auto-push paused." });
  };

  const hasToken = !!storage.cloud.getToken();
  const pushStateLabel = {
    idle: hasToken ? "Idle" : "No token",
    pending: "Saving in 2s…",
    pushing: "Pushing…",
    ok: "Synced",
    err: "Push failed",
  }[pushState.state];

  return (
    <div className="section-block">
      <div className="section-block-title">
        <span className="icon"><IconRefresh size={14} /></span>
        Cloud sync
        <span className="meta">
          {hasToken
            ? <span style={{ color: "var(--success)" }}>● auto-sync on</span>
            : <span style={{ color: "var(--warning)" }}>○ token needed</span>}
        </span>
      </div>

      <div className="small muted" style={{ marginBottom: 14, lineHeight: 1.6 }}>
        The shared JSON URL is pre-configured. To turn on <b>auto-push</b> (your changes save to the Gist automatically), paste your personal GitHub token below.{" "}
        <a style={{ color: "var(--accent-500)", cursor: "pointer" }} onClick={() => setShowHelp(true)}>
          How to get a token →
        </a>
      </div>

      <div className="field" style={{ marginBottom: 12 }}>
        <label>GitHub personal access token</label>
        <div style={{ display: "flex", gap: 6 }}>
          <input
            type={tokenVisible ? "text" : "password"}
            value={token}
            onChange={(e) => setToken(e.target.value)}
            placeholder="github_pat_..."
            style={{ flex: 1, fontFamily: "var(--font-mono)", fontSize: 12 }}
            spellCheck={false}
            autoComplete="off"
          />
          <button className="btn icon-only" onClick={() => setTokenVisible(!tokenVisible)} data-tooltip={tokenVisible ? "Hide" : "Show"}>
            <IconEye size={14} />
          </button>
          <button className="btn" onClick={saveToken}>Save</button>
        </div>
        <div className="small muted" style={{ marginTop: 6 }}>
          Stored only in your browser. Permission needed: <code style={{ background: "var(--bg-surface-2)", padding: "1px 5px", borderRadius: 4 }}>gist: read &amp; write</code>.
        </div>
      </div>

      <details style={{ marginBottom: 12 }}>
        <summary className="small muted" style={{ cursor: "pointer", padding: "4px 0" }}>
          Advanced — shared JSON URL
        </summary>
        <div className="field" style={{ marginTop: 8 }}>
          <div style={{ display: "flex", gap: 6 }}>
            <input
              type="url"
              value={url}
              onChange={(e) => setUrl(e.target.value)}
              placeholder="https://gist.githubusercontent.com/.../raw/db.json"
              style={{ flex: 1, fontFamily: "var(--font-mono)", fontSize: 12 }}
              spellCheck={false}
            />
            <button className="btn" onClick={saveUrl}>Save</button>
          </div>
          <div className="small muted" style={{ marginTop: 4 }}>
            Override only if the team Gist URL changes.
          </div>
        </div>
      </details>

      <div className="row gap-8" style={{ flexWrap: "wrap", alignItems: "center" }}>
        <button className="btn primary" onClick={onPull} disabled={busy || !config.url}>
          <IconRefresh size={13} style={busy ? { animation: "spin 1s linear infinite" } : {}} />
          {busy ? "Pulling…" : "Pull latest"}
        </button>
        <button className="btn" onClick={testPush} disabled={busy || !hasToken}>
          <IconUpload size={13} style={{ transform: "rotate(180deg)" }} />
          Test push
        </button>
        <label className="row gap-8" style={{ cursor: "pointer", userSelect: "none", marginLeft: 8 }}>
          <input
            type="checkbox"
            className="cb"
            checked={config.autoPush !== false}
            onChange={toggleAutoPush}
          />
          <span className="small" style={{ fontWeight: 600 }}>Auto-push on changes</span>
        </label>
        <div style={{ flex: 1 }} />
        <div className="small muted" style={{ textAlign: "right", lineHeight: 1.5 }}>
          {pushState.state !== "idle" && (
            <div style={{
              color:
                pushState.state === "ok" ? "var(--success)" :
                pushState.state === "err" ? "var(--danger)" :
                "var(--text-secondary)",
              fontWeight: 600,
            }}>
              <span className={`push-dot push-${pushState.state}`} /> {pushStateLabel}
            </div>
          )}
          {config.lastPulledAt && <div>Last pull: {CRM.relTime(config.lastPulledAt)}</div>}
          {config.lastPushedAt && <div>Last push: {CRM.relTime(config.lastPushedAt)}</div>}
        </div>
      </div>

      {pushState.error && pushState.state === "err" && (
        <div className="small" style={{ marginTop: 8, color: "var(--danger)" }}>
          ⚠ {pushState.error}
        </div>
      )}

      {status && (
        <div className="small" style={{
          marginTop: 12, padding: "8px 12px", borderRadius: 8,
          background: status.type === "ok" ? "rgba(34, 197, 94, 0.10)" : "rgba(210, 63, 63, 0.10)",
          color: status.type === "ok" ? "var(--success)" : "var(--danger)",
          border: `1px solid ${status.type === "ok" ? "rgba(34, 197, 94, 0.30)" : "rgba(210, 63, 63, 0.30)"}`,
          display: "flex", alignItems: "center", gap: 6, fontWeight: 600,
        }}>
          {status.type === "ok" ? <IconCheck size={13} /> : <IconClose size={13} />}
          {status.msg}
        </div>
      )}

      {showHelp && <CloudHelpModal onClose={() => setShowHelp(false)} />}
    </div>
  );
}

function CloudHelpModal({ onClose }) {
  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()} style={{ width: 580 }}>
        <h2>Enable auto-push to the team Gist</h2>
        <div className="modal-sub">Two minutes. After this, every card you create or edit saves to the shared Gist automatically.</div>

        <ol className="cloud-steps">
          <li>
            Open <a href="https://github.com/settings/personal-access-tokens/new" target="_blank" rel="noopener" style={{ color: "var(--accent-500)" }}>github.com/settings/personal-access-tokens/new</a> while logged in.
          </li>
          <li>
            <b>Token name:</b> <code>OXIDE CRM</code><br />
            <b>Expiration:</b> 1 year (or longer)<br />
            <b>Resource owner:</b> your personal account
          </li>
          <li>
            <b>Repository access:</b> "Public Repositories (read-only)" — leave as-is, we don't need repos.
          </li>
          <li>
            Scroll to <b>"Account permissions"</b>:
            <br />→ Find <b>Gists</b> in the list
            <br />→ Set access to <b>"Read and write"</b>
          </li>
          <li>
            Click <b>Generate token</b> at the bottom. Copy the token (starts with <code>github_pat_…</code>) — you'll only see it once.
          </li>
          <li>
            Paste it into the <b>GitHub personal access token</b> field above and click <b>Save</b>.
          </li>
        </ol>

        <div className="cloud-howto">
          <div className="cloud-howto-block">
            <div className="cloud-howto-title">✏️ When you edit a card</div>
            <div className="small muted">Change saved locally instantly, then auto-pushed to the Gist 2.5 seconds later.</div>
          </div>
          <div className="cloud-howto-block">
            <div className="cloud-howto-title">📥 When you open the app</div>
            <div className="small muted">Latest Gist version pulls automatically. You see what teammates did since you last opened.</div>
          </div>
        </div>

        <div className="small" style={{ marginTop: 14, padding: 10, background: "rgba(217, 119, 6, 0.08)", borderRadius: 8, border: "1px solid rgba(217, 119, 6, 0.25)", color: "var(--warning)", lineHeight: 1.6 }}>
          ⚠️ <b>Conflict risk.</b> If two people edit the same card around the same second, last writer wins. Coordinate on Slack during busy edits.
        </div>

        <div className="small muted" style={{ marginTop: 10, lineHeight: 1.6 }}>
          🔒 <b>Security.</b> Token lives only in your browser localStorage. To revoke, go back to GitHub Settings → Personal access tokens → delete the OXIDE CRM token. Auto-push stops immediately.
        </div>

        <div className="modal-actions">
          <button className="btn primary" onClick={onClose}>Got it</button>
        </div>
      </div>
    </div>
  );
}

// ── Data / backup section (Settings) ──────────────────────
function DataSection() {
  const [stats, setStats] = useS(() => storage.stats());
  const fileRef = useR(null);
  const refresh = () => setStats(storage.stats());

  const onExport = () => {
    const data = storage.export();
    const blob = new Blob([data], { type: "application/json" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = `oxide-backup-${new Date().toISOString().slice(0,10)}.json`;
    a.click();
    URL.revokeObjectURL(url);
  };
  const onImport = (e) => {
    const file = e.target.files?.[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = () => {
      try {
        const count = storage.import(reader.result);
        alert(`Imported ${count} creators successfully.`);
        location.reload();
      } catch (err) {
        alert("Import failed: " + err.message);
      }
    };
    reader.readAsText(file);
  };
  const onReset = () => {
    if (confirm("This will erase all local data and reseed with demo creators. Continue?")) {
      storage.reset();
      location.reload();
    }
  };

  return (
    <div className="section-block">
      <div className="section-block-title">
        <span className="icon"><IconImport size={14} /></span>
        Data &amp; backups
        <span className="meta">backend: <b>{storage.backend}</b></span>
      </div>
      <div className="metrics-grid" style={{ gridTemplateColumns: "repeat(3, 1fr)", marginBottom: 12 }}>
        <MetricTile label="Creators" value={stats.creators} />
        <MetricTile label="Size" value={stats.sizeMb >= 1 ? stats.sizeMb + " MB" : stats.sizeKb + " KB"} />
        <MetricTile label="Seeded" value={CRM.fmtDate(new Date(stats.seededAt))} />
      </div>
      <div className="row gap-8" style={{ flexWrap: "wrap" }}>
        <button className="btn" onClick={onExport}><IconUpload size={13} style={{ transform: "rotate(180deg)" }} /> Export JSON</button>
        <button className="btn" onClick={() => fileRef.current?.click()}><IconUpload size={13} /> Import JSON</button>
        <input ref={fileRef} type="file" accept="application/json" style={{ display: "none" }} onChange={onImport} />
        <div className="spacer" style={{ flex: 1 }} />
        <button className="btn" onClick={onReset} style={{ color: "var(--danger)" }}><IconTrash size={13} /> Reset to demo</button>
      </div>
      <div className="small muted" style={{ marginTop: 10, lineHeight: 1.6 }}>
        Currently storing in <b>{storage.backend}</b>. Use Export to back up, or to migrate to a real backend later.
        See <code style={{ background: "var(--bg-hover)", padding: "1px 5px", borderRadius: 4 }}>SUPABASE_MIGRATION.md</code> for the schema and step-by-step.
      </div>
    </div>
  );
}

// ── Loading splash screen ─────────────────────────────────
function LoadingScreen() {
  return (
    <div className="loading-screen">
      <div className="loading-stage">
        <div className="loading-logo-wrap">
          <svg className="loading-logo" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
            <defs>
              <linearGradient id="blueFlame" x1="0" y1="0" x2="0" y2="64" gradientUnits="userSpaceOnUse">
                <stop offset="0" stopColor="#7eb6ff"/>
                <stop offset="0.5" stopColor="#3b82f6"/>
                <stop offset="1" stopColor="#1e3a8a"/>
              </linearGradient>
              <radialGradient id="blueGlow" cx="0.5" cy="0.7" r="0.6">
                <stop offset="0" stopColor="#bfdbfe" stopOpacity="0.9"/>
                <stop offset="1" stopColor="#3b82f6" stopOpacity="0"/>
              </radialGradient>
            </defs>
            <circle cx="32" cy="36" r="22" fill="url(#blueGlow)" />
            <path
              d="M32 6 C 38 18, 50 22, 48 36 C 47 47, 39 56, 32 56 C 25 56, 17 47, 16 36 C 14 24, 22 22, 24 14 C 25 22, 30 22, 32 6 Z"
              fill="url(#blueFlame)"
            />
            <path
              d="M32 26 C 35 32, 40 34, 39 42 C 38 48, 35 52, 32 52 C 29 52, 26 48, 25 42 C 24 36, 28 34, 32 26 Z"
              fill="#bfdbfe"
              opacity="0.55"
            />
          </svg>
        </div>
        <h1 className="loading-text">
          <span>We're here</span>
          <span>so everyone hears about us</span>
        </h1>
        <div className="loading-bar"><div className="loading-bar-fill" /></div>
      </div>
    </div>
  );
}

// ── Sign-in screen (passphrase-based local auth) ──────────
function SignInScreen({ onSignIn, theme, onTheme }) {
  const [passphrase, setPassphrase] = useS("");
  const [error, setError] = useS("");
  const [shake, setShake] = useS(false);
  const [revealHint, setRevealHint] = useS(false);
  const inputRef = useR(null);

  const tryLogin = () => {
    const user = storage.auth.verify(passphrase);
    if (user) {
      onSignIn(user);
    } else {
      setError("Passphrase not recognized. Check the dashes and spelling.");
      setShake(true);
      setTimeout(() => setShake(false), 420);
      inputRef.current?.select();
    }
  };

  return (
    <div className="signin-bg">
      <div className={`signin-card ${shake ? "shake" : ""}`}>
        <div className="signin-brand">
          <img src="assets/logo.png" alt="OXIDE" width="40" height="40" style={{ borderRadius: 8, display: "block" }} />
          <div>
            <div style={{ fontWeight: 800, fontSize: 18, letterSpacing: "-0.01em" }}>
              OXIDE<span style={{ color: "var(--text-tertiary)", fontWeight: 600 }}>: Influencers</span>
            </div>
            <div className="small muted">Creator partnership CRM</div>
          </div>
          <button className="btn ghost icon-only" onClick={onTheme} style={{ marginLeft: "auto" }} data-tooltip="Toggle theme">
            {theme === "dark" ? <IconSun size={16} /> : <IconMoon size={16} />}
          </button>
        </div>

        <h1 style={{ fontSize: 22, fontWeight: 700, letterSpacing: "-0.02em", margin: "26px 0 6px" }}>
          Welcome back
        </h1>
        <p className="muted" style={{ fontSize: 13, margin: 0, lineHeight: 1.55 }}>
          Enter your personal access phrase. Each team member has a unique key — share-resistant,
          memorable, and rotatable.
        </p>

        <div className="passphrase-field" style={{ marginTop: 22 }}>
          <label className="small" style={{ fontWeight: 600, color: "var(--text-tertiary)", textTransform: "uppercase", letterSpacing: "0.05em", fontSize: 11 }}>
            Access phrase
          </label>
          <div className={`passphrase-input ${error ? "error" : ""}`}>
            <IconLightning size={14} className="muted" />
            <input
              ref={inputRef}
              autoFocus
              type="text"
              value={passphrase}
              onChange={(e) => { setPassphrase(e.target.value); setError(""); }}
              onKeyDown={(e) => e.key === "Enter" && tryLogin()}
              placeholder="word-word-0000-word"
              autoComplete="off"
              spellCheck={false}
            />
            {passphrase && (
              <button className="btn ghost icon-only sm" onClick={() => setPassphrase("")} data-tooltip="Clear">
                <IconClose size={12} />
              </button>
            )}
          </div>
          {error && (
            <div className="small" style={{ color: "var(--danger)", marginTop: 8, display: "flex", alignItems: "center", gap: 6 }}>
              <IconClose size={12} /> {error}
            </div>
          )}
        </div>

        <button
          className="btn primary"
          onClick={tryLogin}
          disabled={!passphrase}
          style={{ width: "100%", justifyContent: "center", padding: "11px 14px", marginTop: 14, fontSize: 14 }}
        >
          <IconChevron size={14} /> Unlock workspace
        </button>

        <div className="signin-divider"><span>Format</span></div>
        <div className="passphrase-format">
          <span className="pf-word">word</span>
          <span className="pf-sep">–</span>
          <span className="pf-word">word</span>
          <span className="pf-sep">–</span>
          <span className="pf-word pf-num">0000</span>
          <span className="pf-sep">–</span>
          <span className="pf-word">word</span>
        </div>
        <div className="small muted" style={{ marginTop: 10, textAlign: "center", lineHeight: 1.55 }}>
          Got your phrase from the team lead via Slack or 1Password.<br />
          Don't have one? <a style={{ color: "var(--accent-500)", cursor: "pointer" }} onClick={() => setRevealHint(true)}>I need help</a>
        </div>

        {revealHint && (
          <div className="modal-overlay" onClick={() => setRevealHint(false)}>
            <div className="modal" onClick={(e) => e.stopPropagation()} style={{ width: 440 }}>
              <h2>How access works</h2>
              <div className="modal-sub">Four team members, four unique phrases. Stored locally, no servers involved.</div>
              <ul style={{ paddingLeft: 18, margin: "12px 0", fontSize: 13, color: "var(--text-secondary)", lineHeight: 1.7 }}>
                <li>Phrases are <b>not stored in plain config</b> on production — they're encoded into the build.</li>
                <li>Don't share your phrase. Each one is tied to a specific team member.</li>
                <li>Lost your phrase? Ping the team lead — they can rotate yours in 30 seconds.</li>
                <li>When we move to Supabase, this gets replaced with Google SSO.</li>
              </ul>
              <div className="small muted" style={{ background: "var(--bg-surface-2)", padding: 10, borderRadius: 8, fontFamily: "var(--font-mono)" }}>
                Format example: <b style={{ color: "var(--text-primary)" }}>word-word-1234-word</b>
              </div>
              <div className="modal-actions">
                <button className="btn primary" onClick={() => setRevealHint(false)}>Got it</button>
              </div>
            </div>
          </div>
        )}

        <div className="signin-foot small muted">
          Session stored in this browser only. Clear it from Settings or sign out anytime.
        </div>
      </div>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
