/* global React, ReactDOM, Sidebar, Topbar, SearchPage, LeadsPage, FlowPage, DashboardPage, Icons,
   TweaksPanel, useTweaks, TweakSection, TweakRadio, TweakToggle, TweakSlider, TweakColor,
   ConnectionsPage, ScriptPage, AuthGate */
const { useState, useEffect, useMemo, useRef } = React;

// ── Supabase data helpers ──────────────────────────────────────────────────────

async function loadUserData(userId) {
  const { data, error } = await window._sb
    .from('dashboard_data')
    .select('scripts, leads, settings')
    .eq('user_id', userId)
    .maybeSingle();
  if (error) { console.warn('[AIP] Supabase load error:', error.message); return null; }
  return data;
}

async function saveUserData(userId, scripts, leads, settings) {
  const { error } = await window._sb
    .from('dashboard_data')
    .upsert({ user_id: userId, scripts, leads, settings, updated_at: new Date().toISOString() });
  if (error) console.warn('[AIP] Supabase save error:', error.message);
}

const SETTINGS_KEYS = [
  'aip_leads_cols', 'aip_cc_cols', 'aip_quick_copy_order',
  'aip_sort_key', 'aip_sort_dir', 'aip_saved_searches',
  'aip_qc_profiles', 'aip_qc_active_profile', 'aip_qc_headers',
  'aip_script_prompts',
];

function gatherSettings() {
  const s = {};
  SETTINGS_KEYS.forEach(k => { const v = localStorage.getItem(k); if (v !== null) s[k] = v; });
  return s;
}

function applySettings(settings) {
  if (!settings) return;
  Object.entries(settings).forEach(([k, v]) => localStorage.setItem(k, v));
}

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "theme": "dark",
  "accentHue": 240,
  "density": "comfortable",
  "glowIntensity": 0.6,
  "showGlow": true
}/*EDITMODE-END*/;

function StubPage({ title, blurb }) {
  return (
    <div className="page">
      <div className="page-header">
        <div className="page-title-block">
          <h1>{title}</h1>
          <div className="subtitle">{blurb}</div>
        </div>
      </div>
      <div className="card" style={{ padding: 60, textAlign: 'center' }}>
        <div style={{ width: 56, height: 56, margin: '0 auto', borderRadius: 14, background: 'var(--accent-soft)', color: 'var(--accent)', display: 'grid', placeItems: 'center' }}>
          <Icons.bolt size={26}/>
        </div>
        <div style={{ marginTop: 16, fontWeight: 600, fontSize: 15 }}>{title} — coming soon</div>
        <div style={{ color: 'var(--text-2)', fontSize: 13, marginTop: 4, maxWidth: 420, marginLeft: 'auto', marginRight: 'auto' }}>
          {blurb}
        </div>
      </div>
    </div>
  );
}

function ExtensionPage() {
  return (
    <div className="page">
      <div className="page-header">
        <div className="page-title-block">
          <h1>Browser Extension</h1>
          <div className="subtitle">Add Amazon products to AIP Analyzer with one click — straight from any product page.</div>
        </div>
        <div className="page-actions">
          <button className="btn btn-primary"><Icons.download size={13}/> Install for Chrome</button>
        </div>
      </div>

      <div className="card" style={{ padding: 28, position: 'relative', overflow: 'hidden', minHeight: 360 }}>
        <div style={{ position: 'absolute', inset: 0,
          background: 'radial-gradient(ellipse 60% 80% at 100% 0%, oklch(0.55 0.20 250 / 0.35), transparent 60%)',
          pointerEvents: 'none' }}/>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 32, position: 'relative', alignItems: 'center' }}>
          <div>
            <span className="badge badge-accent"><span className="badge-dot"/>v0.4 beta</span>
            <div style={{ fontSize: 24, fontWeight: 700, marginTop: 12, letterSpacing: '-0.4px' }}>One‑click capture, anywhere on Amazon</div>
            <div style={{ color: 'var(--text-2)', marginTop: 8, lineHeight: 1.6 }}>
              Browse Amazon like normal. Hit the AIP icon — we pull title, ASIN, price, BSR, reviews and rating, then drop it into whichever lead list you choose.
            </div>
            <div style={{ display: 'grid', gap: 8, marginTop: 18 }}>
              {['Detects ASIN automatically from product pages','Captures BSR, reviews, rating, and category','Choose target list before save','Tags pulled from your custom rules'].map(t => (
                <div key={t} style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
                  <span style={{ width: 18, height: 18, borderRadius: 999, display: 'grid', placeItems: 'center', background: 'var(--accent-soft)', color: 'var(--accent)', flexShrink: 0 }}>
                    <Icons.check size={11}/>
                  </span>
                  <span style={{ fontSize: 13 }}>{t}</span>
                </div>
              ))}
            </div>
          </div>
          {/* mock extension preview */}
          <div style={{
            background: 'linear-gradient(180deg, var(--bg-2), var(--bg-1))',
            border: '1px solid var(--line-strong)',
            borderRadius: 16,
            padding: 18,
            boxShadow: '0 30px 80px -30px rgba(0,0,0,0.7)',
          }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 11, color: 'var(--text-3)' }}>
              <span style={{ width: 8, height: 8, borderRadius: 99, background: 'var(--danger)' }}/>
              <span style={{ width: 8, height: 8, borderRadius: 99, background: 'var(--warn)' }}/>
              <span style={{ width: 8, height: 8, borderRadius: 99, background: 'var(--success)' }}/>
              <span style={{ marginLeft: 12, fontFamily: 'var(--font-mono)' }}>amazon.com/dp/B0F2C9V3D9</span>
            </div>
            <div style={{ marginTop: 14, padding: 14, background: 'var(--bg-0)', borderRadius: 10, border: '1px solid var(--line)' }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                <div className="brand-mark" style={{ width: 28, height: 28 }}><span style={{ fontSize: 9 }}>AIP</span></div>
                <div style={{ flex: 1, fontWeight: 600, fontSize: 13 }}>Save to AIP Analyzer</div>
                <Icons.close size={14} style={{ color: 'var(--text-3)' }}/>
              </div>
              <div style={{ fontSize: 12, color: 'var(--text-1)', marginTop: 12, lineHeight: 1.5 }}>
                <b>Robinair 13204 Premium High Vacuum Pump Oil — 1 Gallon</b>
              </div>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8, marginTop: 12 }}>
                <div><div style={{ fontSize: 10, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: 1 }}>Price</div><div className="num" style={{ fontWeight: 600 }}>$58.67</div></div>
                <div><div style={{ fontSize: 10, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: 1 }}>BSR</div><div className="num" style={{ color: 'var(--text-2)' }}>#23,415</div></div>
                <div><div style={{ fontSize: 10, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: 1 }}>ROI</div><div><span className="badge badge-success">40%</span></div></div>
                <div><div style={{ fontSize: 10, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: 1 }}>Rating</div><div style={{ color: 'var(--accent)' }}>★ 4.6</div></div>
              </div>
              <div style={{ marginTop: 12 }}>
                <div style={{ fontSize: 10, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: 1, marginBottom: 6 }}>Add to list</div>
                <div className="list-pill active"><span className="dot-sq" style={{ background: '#4cc9f0' }}/><span className="name">High ROI</span></div>
              </div>
              <button className="btn btn-primary" style={{ width: '100%', justifyContent: 'center', marginTop: 12 }}>
                <Icons.plus size={13}/> Save lead
              </button>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

const LEADS_STORAGE_KEY = 'aip_leads';

const DEFAULT_LEADS = {
  lists: [
    { id: 'high-roi', name: 'High ROI Picks', color: '#7c5cff' },
    { id: 'trending', name: 'Trending',        color: '#22d3ee' },
    { id: 'review',   name: 'In Review',       color: '#f59e0b' },
  ],
  products: [],
  customCols: [],
  notes: {},
};

function loadStoredLeads() {
  try {
    const stored = localStorage.getItem(LEADS_STORAGE_KEY);
    if (stored) {
      const data = JSON.parse(stored);
      // Migrate old single `list` field → `lists` array
      data.products = (data.products || []).map(p => {
        if (!p.lists) {
          return { ...p, lists: p.list ? [p.list] : [], list: undefined };
        }
        return p;
      });
      return data;
    }
  } catch (_) {}
  return DEFAULT_LEADS;
}

function loadStoredImport() {
  try { return JSON.parse(localStorage.getItem('aip_cc_import') || 'null'); }
  catch (_) { return null; }
}

function loadStoredScripts() {
  try { return JSON.parse(localStorage.getItem('aip_scripts') || '[]'); }
  catch (_) { return []; }
}

/* If localStorage metadata is missing but IDB has records, reconstruct metadata */
async function recoverFromIDB(setImportStored) {
  try {
    const db = await new Promise((resolve, reject) => {
      // Must open at DB_VERSION=2 — opening at a lower version than the current
      // DB version throws a VersionError and silently aborts the recovery.
      const req = indexedDB.open('aip_db', 2);
      req.onupgradeneeded = e => {
        const db2 = e.target.result;
        let store;
        if (e.oldVersion < 1) {
          store = db2.createObjectStore('cc_records', { keyPath: 'asin' });
        } else {
          store = e.target.transaction.objectStore('cc_records');
        }
        if (!store.indexNames.contains('commission')) {
          store.createIndex('commission', 'commission', { unique: false });
        }
      };
      req.onsuccess = e => resolve(e.target.result);
      req.onerror   = e => reject(e.target.error);
    });
    const count = await new Promise((resolve, reject) => {
      const req = db.transaction('cc_records', 'readonly').objectStore('cc_records').count();
      req.onsuccess = e => resolve(e.target.result);
      req.onerror   = e => reject(e.target.error);
    });
    if (count > 0) {
      const meta = { totalAsins: count, avgCommission: 0, maxCommission: 0, totalBrands: 0, topRecords: [], importedAt: Date.now(), fileCount: 1, recovered: true };
      localStorage.setItem('aip_cc_import', JSON.stringify(meta));
      setImportStored(meta);
    }
  } catch (_) {}
}

function App({ session, onSignOut }) {
  const userId = session.user.id;

  const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [page, setPage] = useState('leads');
  const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
  const [dataLoading, setDataLoading] = useState(true);

  /* Import state lives here so it survives page navigation */
  const [importStored, setImportStored] = useState(() => loadStoredImport());
  const [importStatus, setImportStatus] = useState(null); // { phase, current, total, error }

  /* On mount — if localStorage has no metadata but IDB has records, auto-recover */
  useEffect(() => {
    if (!loadStoredImport()) recoverFromIDB(setImportStored);
  }, []);

  const [leads, setLeads] = useState(DEFAULT_LEADS);
  const [scripts, setScripts] = useState([]);

  /* Load data from Supabase on login; fall back to localStorage if no cloud data yet */
  useEffect(() => {
    loadUserData(userId).then(data => {
      if (data) {
        if (Array.isArray(data.scripts) && data.scripts.length) setScripts(data.scripts);
        else setScripts(loadStoredScripts());
        if (data.leads && data.leads.products) setLeads(data.leads);
        else setLeads(loadStoredLeads());
        applySettings(data.settings); // restore preferences before spinner clears
      } else {
        // First login — migrate any existing localStorage data
        setScripts(loadStoredScripts());
        setLeads(loadStoredLeads());
      }
      setDataLoading(false);
    });
  }, [userId]);

  /* Debounced save to Supabase whenever scripts or leads change */
  const saveTimer = useRef(null);
  useEffect(() => {
    if (dataLoading) return; // don't save during initial load
    if (saveTimer.current) clearTimeout(saveTimer.current);
    saveTimer.current = setTimeout(() => {
      saveUserData(userId, scripts, leads, gatherSettings());
    }, 2000);
    return () => clearTimeout(saveTimer.current);
  }, [scripts, leads, dataLoading]);

  /* Periodic save every 60s to catch settings-only changes */
  const scriptsRef = useRef(scripts);
  const leadsRef   = useRef(leads);
  useEffect(() => { scriptsRef.current = scripts; }, [scripts]);
  useEffect(() => { leadsRef.current = leads; }, [leads]);
  useEffect(() => {
    if (dataLoading) return;
    const interval = setInterval(() => {
      saveUserData(userId, scriptsRef.current, leadsRef.current, gatherSettings());
    }, 60000);
    return () => clearInterval(interval);
  }, [dataLoading, userId]);

  /* Sync leads list metadata → chrome.storage so the extension popup can read it */
  useEffect(() => {
    const meta = leads.lists.map(({ id, name, color }) => ({ id, name, color }));
    window.dispatchEvent(new CustomEvent('aip:leadsMetaChanged', { detail: meta }));
  }, [leads.lists]);

  /* Keepa export → add products to the chosen leads list */
  useEffect(() => {
    const handler = (e) => {
      try {
      const { products = [], targetListIds = [] } = e.detail;
      // Inbox is always included (represents last export); plus any user-selected lists
      const exportListIds = ['inbox', ...targetListIds];
      const today  = new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
      const mapped = products.map(p => {
        const price       = p.price        || 0;
        const monthlySales = p.monthlySold || 0;
        const commission  = p.commission   || 0;
        return {
          id:              `kp-${p.asin}`,
          name:            p.title || p.asin,
          asin:            p.asin,
          image:           p.image        || null,
          brand:           p.brandName    || '',
          category:        p.category     || '',
          source:          'Keepa',
          price,
          monthlySales,
          bsr:             p.salesRank    || 0,
          reviews:         p.reviews      || 0,
          rating:          p.rating       || 0,
          variations:      p.variations   || 0,
          cc:              commission ? Math.round(commission * 100) : 0,
          campaignStart:   p.campaignStartDate || '',
          campaignEnd:     p.campaignEndDate   || '',
          campaignId:      p.campaignId        || '',
          budget:          p.budget            || 0,
          remainingBudget: p.budgetRemaining   || 0,
          totalSlots:      p.totalSlots        ?? null,
          availableSlots:  p.availableSlots    ?? null,
          // Calculated fields
          revenue30d:   price && monthlySales ? +(price * monthlySales).toFixed(2) : 0,
          ccCommission: price && commission   ? +(price * commission).toFixed(2)   : 0,
          videos:           p.videos      || 0,
          hasMainVideo:    p.hasMainVideo || false,
          cost: 0, profit: 0, roi: 0,
          onSite:           p.onsiteRate != null ? +(p.onsiteRate * 100).toFixed(2) : 1,
          onSiteCommission: price && p.onsiteRate ? +(price * p.onsiteRate).toFixed(2) : 0,
          listingAge:       p.listingAge ?? 0,
          tags: [], trend: [],
          lists:     exportListIds,
          addedDate: today,
        };
      });
      setLeads(prev => {
        const importedAsins  = new Set(mapped.map(mp => mp.asin));
        const existingByAsin = Object.fromEntries(prev.products.map(p => [p.asin, p]));

        // Products NOT in this import: keep as-is, but clear stale inbox tag
        const kept = prev.products
          .filter(p => !importedAsins.has(p.asin))
          .map(p => {
            const lists = p.lists || (p.list ? [p.list] : []);
            return { ...p, lists: lists.filter(lid => lid !== 'inbox') };
          });

        // Imported products: update data, merge lists for existing ASINs; new ones get exportListIds
        const upserted = mapped.map(mp => {
          const existing = existingByAsin[mp.asin];
          if (existing) {
            const existingLists = existing.lists || (existing.list ? [existing.list] : []);
            const mergedLists = [...new Set([...existingLists, ...exportListIds])];
            return { ...mp, lists: mergedLists, addedDate: existing.addedDate };
          }
          return mp; // new ASIN → lists: exportListIds
        });

        return { ...prev, products: [...kept, ...upserted] };
      });
      setPage('leads');
      } catch (err) {
        console.error('[AIP] keepa handler ERROR:', err);
      }
    };
    window.addEventListener('aip:keepaExport', handler);
    return () => window.removeEventListener('aip:keepaExport', handler);
  }, []);

  /* Script Brief import: extension → dashboard-bridge → this handler */
  useEffect(() => {
    const handler = (e) => {
      const data = e.detail;
      if (!data?.asin) return;
      const id = 'sc-' + Date.now();
      const product = {
        id,
        asin:            data.asin,
        url:             data.url             || '',
        brand:           data.brand           || '',
        title:           data.title           || '',
        features:        data.features        || '',
        productDesc:     data.productDesc     || '',
        price:           data.price           || 0,
        image:           data.image           || '',
        customerReviewsSummary: data.customerReviewsSummary || '',
        reviewTopics:    data.reviewTopics    || '',
        suggestedQuestions: data.suggestedQuestions || '',
        buyerFears:      data.buyerFears      || '',
        misconceptions:  data.misconceptions  || '',
        firstFiveSeconds: data.firstFiveSeconds || '',
        personalNotes:   '',
        buyerPersona:    '',
        videoGaps:       '',
        demonstration:   '',
        status:          'scraped',
        scrapedAt:       Date.now(),
      };
      setScripts(prev => {
        // Re-scraping same ASIN refreshes scraped fields but keeps manual notes
        const idx = prev.findIndex(p => p.asin === product.asin);
        if (idx >= 0) {
          const next = [...prev];
          next[idx] = {
            ...next[idx],
            brand: product.brand, title: product.title, features: product.features,
            productDesc: product.productDesc,
            price: product.price, image: product.image,
            customerReviewsSummary: product.customerReviewsSummary,
            reviewTopics: product.reviewTopics,
            suggestedQuestions: product.suggestedQuestions,
            buyerFears: product.buyerFears, misconceptions: product.misconceptions,
            firstFiveSeconds: product.firstFiveSeconds,
          };
          return next;
        }
        return [product, ...prev];
      });
      setPage('script');
    };
    window.addEventListener('aip:scriptImport', handler);
    return () => window.removeEventListener('aip:scriptImport', handler);
  }, []);

  // theme
  useEffect(() => {
    document.documentElement.setAttribute('data-theme', tweaks.theme);
  }, [tweaks.theme]);

  // density
  useEffect(() => {
    const root = document.documentElement;
    if (tweaks.density === 'compact') root.style.setProperty('--row-pad', '8px');
    else root.style.setProperty('--row-pad', '14px');
  }, [tweaks.density]);

  // accent hue
  useEffect(() => {
    const root = document.documentElement;
    const h = tweaks.accentHue;
    if (tweaks.theme === 'dark') {
      root.style.setProperty('--accent', `oklch(0.78 0.18 ${h})`);
      root.style.setProperty('--accent-2', `oklch(0.66 0.22 ${h+10})`);
      root.style.setProperty('--accent-soft', `oklch(0.78 0.18 ${h} / 0.16)`);
      root.style.setProperty('--accent-glow', `oklch(0.78 0.18 ${h} / ${0.45 * tweaks.glowIntensity})`);
    } else {
      root.style.setProperty('--accent', `oklch(0.55 0.20 ${h})`);
      root.style.setProperty('--accent-2', `oklch(0.46 0.24 ${h+10})`);
      root.style.setProperty('--accent-soft', `oklch(0.55 0.20 ${h} / 0.14)`);
      root.style.setProperty('--accent-glow', `oklch(0.55 0.20 ${h} / ${0.22 * tweaks.glowIntensity})`);
    }
  }, [tweaks.accentHue, tweaks.theme, tweaks.glowIntensity]);

  const [showCreatorNotice, setShowCreatorNotice] = useState(() => !localStorage.getItem('aip_creator_id'));

  /* Show loading screen while fetching data from Supabase — must be after all hooks */
  if (dataLoading) {
    return (
      <div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center',
        justifyContent: 'center', background: '#0a0e1a', flexDirection: 'column', gap: 14 }}>
        <div style={{ width: 36, height: 36, border: '3px solid rgba(120,160,255,0.15)',
          borderTopColor: 'oklch(0.78 0.18 240)', borderRadius: '50%',
          animation: 'spin 0.8s linear infinite' }}/>
        <div style={{ fontSize: 13, color: '#8590ad' }}>Loading your data…</div>
      </div>
    );
  }

  const leadsCount = leads.products.length;

  let pageContent;
  switch (page) {
    case 'dashboard': pageContent = <DashboardPage leads={leads} setPage={setPage}/>; break;
    case 'search': pageContent = <SearchPage leads={leads} setLeads={setLeads}/>; break;
    case 'leads': pageContent = <LeadsPage leads={leads} setLeads={setLeads}/>; break;
    case 'flow': pageContent = <FlowPage leads={leads} setLeads={setLeads} setPage={setPage}/>; break;
    case 'extension': pageContent = <ExtensionPage/>; break;
    case 'campaigns': pageContent = <StubPage title="Campaigns" blurb="Active Creator Connections deals, payouts, and content deadlines."/>; break;
    case 'analytics': pageContent = <StubPage title="Analytics" blurb="Conversion, click‑through, and earnings broken down by lead, list, and campaign."/>; break;
    case 'connections': pageContent = <ConnectionsPage stored={importStored} setStored={setImportStored} status={importStatus} setStatus={setImportStatus}/>; break;
    case 'script': pageContent = <ScriptPage scripts={scripts} setScripts={setScripts}/>; break;
    case 'settings': pageContent = <StubPage title="Settings" blurb="Profile, integrations, payout details, and team access."/>; break;
    default: pageContent = <SearchPage leads={leads} setLeads={setLeads}/>;
  }

  return (
    <>
      <div className="app-bg" style={{ opacity: tweaks.showGlow ? 1 : 0, transition: 'opacity 0.3s' }}/>
      <div className="app" style={{ gridTemplateColumns: sidebarCollapsed ? '56px 1fr' : '232px 1fr' }}>
        <Sidebar page={page} setPage={setPage} leadsCount={leadsCount} collapsed={sidebarCollapsed} setCollapsed={setSidebarCollapsed}/>
        <div className="main">
          <Topbar page={page} theme={tweaks.theme} setTheme={(t) => setTweak('theme', t)} onSignOut={onSignOut}/>
          {/* Persistent import-in-progress banner */}
          {importStatus && !importStatus.error && importStatus.phase !== 'done' && page !== 'connections' && (
            <div style={{
              margin: '0 0 0 0', padding: '10px 24px',
              background: 'var(--accent-soft)', borderBottom: '1px solid var(--accent)',
              display: 'flex', alignItems: 'center', gap: 12, fontSize: 13,
            }}>
              <Icons.bolt size={14} style={{ color: 'var(--accent)', animation: 'spin 1s linear infinite', flexShrink: 0 }}/>
              <span>CC import in progress — <b>{importStatus.phase === 'parsing' ? `parsing file ${importStatus.current} of ${importStatus.total}` : importStatus.phase}…</b></span>
              <button className="btn btn-sm" style={{ marginLeft: 'auto' }} onClick={() => setPage('connections')}>View progress</button>
            </div>
          )}
          {showCreatorNotice && (
            <div style={{
              padding: '10px 24px', borderBottom: '1px solid var(--line)',
              display: 'flex', alignItems: 'center', gap: 12, fontSize: 13,
              background: 'var(--bg-2)',
            }}>
              <span style={{ color: 'var(--text-2)', flexShrink: 0 }}>ℹ</span>
              <span>To enable CC campaign links, <a href="https://affiliate-program.amazon.com/p/connect/requests" target="_blank" rel="noreferrer" style={{ color: 'var(--accent)' }}>visit the Creator Connections portal</a> once — the extension will capture your ID automatically.</span>
              <button className="icon-btn" style={{ marginLeft: 'auto', flexShrink: 0 }} onClick={() => setShowCreatorNotice(false)}><Icons.close size={13}/></button>
            </div>
          )}
          {pageContent}
        </div>
      </div>

      <TweaksPanel title="Tweaks">
        <TweakSection title="Theme">
          <TweakRadio
            value={tweaks.theme}
            onChange={(v) => setTweak('theme', v)}
            options={[{ value: 'dark', label: 'Dark' }, { value: 'light', label: 'Light' }]}
          />
        </TweakSection>

        <TweakSection title="Accent hue">
          <TweakSlider min={180} max={320} step={5}
            value={tweaks.accentHue}
            onChange={(v) => setTweak('accentHue', v)}
            label={tweaks.accentHue + '°'}
          />
          <div style={{ fontSize: 11, color: 'var(--text-3)', marginTop: 6 }}>180° cyan · 240° electric blue · 280° purple</div>
        </TweakSection>

        <TweakSection title="Density">
          <TweakRadio
            value={tweaks.density}
            onChange={(v) => setTweak('density', v)}
            options={[{ value: 'comfortable', label: 'Comfortable' }, { value: 'compact', label: 'Compact' }]}
          />
        </TweakSection>

        <TweakSection title="Background glow">
          <TweakToggle value={tweaks.showGlow} onChange={(v) => setTweak('showGlow', v)} label="Show radial glow"/>
          <div style={{ height: 10 }}/>
          <TweakSlider min={0.2} max={1.4} step={0.1}
            value={tweaks.glowIntensity}
            onChange={(v) => setTweak('glowIntensity', v)}
            label={tweaks.glowIntensity.toFixed(1) + '×'}
          />
        </TweakSection>
      </TweaksPanel>
    </>
  );
}

// ── Root: handles auth gate before rendering the app ──────────────────────────

function Root() {
  const [session, setSession]       = useState(null);
  const [authChecked, setAuthChecked] = useState(false);

  useEffect(() => {
    // Check for an existing session on load
    window._sb.auth.getSession().then(({ data: { session } }) => {
      setSession(session);
      setAuthChecked(true);
    });
    // Keep session in sync (login / logout / token refresh)
    const { data: { subscription } } = window._sb.auth.onAuthStateChange((_event, session) => {
      setSession(session);
    });
    return () => subscription.unsubscribe();
  }, []);

  async function handleSignOut() {
    await window._sb.auth.signOut();
    setSession(null);
  }

  if (!authChecked) return null; // brief check before first render
  if (!session)    return <AuthGate onAuth={setSession}/>;
  return <App session={session} onSignOut={handleSignOut}/>;
}

ReactDOM.createRoot(document.getElementById('root')).render(<Root/>);
