// ============================================================
// app.jsx — Top-level shell, three modes: Atlas / Strain / Blend
//
// Boots with the bundled 29-strain fallback (immediate render),
// then awaits CannabisData.load() and re-renders with the live
// Supabase dataset (top-N by popularity).
// ============================================================
(function () {
const { useState, useEffect, useMemo, useRef } = React;
const {
  TERPENE_KEYS, TERPENE_META, TERPENE_P95, CATEGORY_COLORS,
  STRAINS, findSimilar, projectBlendToPCA,
} = window.CannabisData;
const { AtlasMap, TerpeneRadar, StrainCard, BlendBuilder, clamp } = window.PhytoUI;

// ── App ─────────────────────────────────────────────────────
function App() {
  const [mode, setMode] = useState('atlas');            // atlas | strain | blend
  const [selected, setSelected] = useState('og-kush');  // strain slug
  const [hovered, setHovered] = useState(null);
  const [query, setQuery] = useState('');
  const [categoryFilter, setCategoryFilter] = useState('All');
  const [colorMode, setColorMode] = useState('category');
  const [similarityThreshold, setSimThreshold] = useState(0.85);
  const [blendVals, setBlendVals] = useState(() =>
    // start with a moderate myrcene-cary blend
    [0.40, 0.40, 0.20, 0.03, 0.10, 0.10, 0.08, 0.13, 0.03, 0.03, 0.04]
  );
  // dataTick forces re-renders when STRAINS is mutated by the Supabase loader
  const [dataTick, setDataTick] = useState(0);
  const [dataSource, setDataSource] = useState('fallback');
  const [dataError, setDataError] = useState(null);
  // Mobile drawer: null | 'browse' (left rail) | 'detail' (right rail)
  const [drawer, setDrawer] = useState(null);

  // Fire-and-forget live load on mount
  useEffect(() => {
    let cancelled = false;
    window.CannabisData.load()
      .then(res => {
        if (cancelled) return;
        setDataSource(res.source);
        setDataTick(t => t + 1);
        // If the previously-selected slug isn't in the live dataset, fall back to
        // the most popular live strain so the right rail / strain view aren't empty.
        if (!STRAINS.find(s => s.slug === selected)) {
          setSelected(STRAINS[0]?.slug ?? null);
        }
      })
      .catch(err => {
        if (cancelled) return;
        console.warn('[phytonomy] Supabase load failed:', err);
        setDataError(String(err.message ?? err));
      });
    return () => { cancelled = true; };
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const sel = STRAINS.find(s => s.slug === selected);

  const searchResults = useMemo(() => {
    const all = STRAINS.slice();
    let filtered = all;
    if (query.trim()) {
      const q = query.toLowerCase();
      filtered = all.filter(s =>
        s.name.toLowerCase().includes(q) ||
        s.cat.toLowerCase().includes(q) ||
        (s.dom || '').includes(q)
      );
    }
    return filtered.sort((a, b) => (b.popularity ?? 0) - (a.popularity ?? 0));
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [query, dataTick]);

  const similar = useMemo(() =>
    sel ? findSimilar(sel, 8, 0.7) : [],
  // eslint-disable-next-line react-hooks/exhaustive-deps
  [sel, dataTick]);

  const blendPCA = useMemo(() => projectBlendToPCA(blendVals), [blendVals, dataTick]);

  const handleSelectStrain = (s) => {
    setSelected(s.slug);
    if (mode === 'blend') setMode('strain');
    setDrawer(null);   // close any open mobile drawer after picking
  };

  return (
    <div className="app">
      <Header
        mode={mode}
        setMode={(m) => { setMode(m); setDrawer(null); }}
        liveCount={STRAINS.length}
        dataSource={dataSource}
        dataError={dataError}
        setDrawer={setDrawer}
        hasSelection={!!sel}
      />

      <main className="main">
        <LeftRail
          query={query} setQuery={setQuery}
          categoryFilter={categoryFilter} setCategoryFilter={setCategoryFilter}
          colorMode={colorMode} setColorMode={setColorMode}
          similarityThreshold={similarityThreshold} setSimThreshold={setSimThreshold}
          searchResults={searchResults}
          selected={selected}
          onSelectStrain={handleSelectStrain}
          mode={mode}
          drawerOpen={drawer === 'browse'}
          closeDrawer={() => setDrawer(null)}
        />

        <section className="canvas">
          {mode === 'atlas' && (
            <AtlasView
              selected={selected}
              setSelected={handleSelectStrain}
              setHovered={setHovered}
              similarityThreshold={similarityThreshold}
              categoryFilter={categoryFilter}
              colorMode={colorMode}
              hovered={hovered}
              sel={sel}
              dataTick={dataTick}
            />
          )}
          {mode === 'strain' && (
            <StrainView sel={sel} similar={similar} onPick={handleSelectStrain} />
          )}
          {mode === 'blend' && (
            <BlendView
              values={blendVals}
              setValues={setBlendVals}
              onSelect={handleSelectStrain}
              selected={selected}
              blendPCA={blendPCA}
              similarityThreshold={similarityThreshold}
              categoryFilter={categoryFilter}
              colorMode={colorMode}
              sel={sel}
              dataTick={dataTick}
            />
          )}
        </section>

        <RightRail
          sel={sel} similar={similar} onPick={handleSelectStrain}
          drawerOpen={drawer === 'detail'}
          closeDrawer={() => setDrawer(null)}
        />
      </main>

      <Footer />

      {drawer && <div className="drawer-backdrop" onClick={() => setDrawer(null)} />}
    </div>
  );
}

// ============================================================
function Header({ mode, setMode, liveCount, dataSource, dataError, setDrawer, hasSelection }) {
  const isLive = dataSource === 'supabase';
  return (
    <header className="header">
      <button className="hdr-mobile-btn hamburger" aria-label="Browse cultivars"
        onClick={() => setDrawer('browse')}>
        <svg width="18" height="18" viewBox="0 0 18 18" aria-hidden="true">
          <g stroke="currentColor" strokeWidth="1.5" strokeLinecap="round">
            <line x1="2.5" y1="5" x2="15.5" y2="5" />
            <line x1="2.5" y1="9" x2="15.5" y2="9" />
            <line x1="2.5" y1="13" x2="15.5" y2="13" />
          </g>
        </svg>
      </button>

      <a href="../" className="brand" style={{ textDecoration: 'none', color: 'inherit' }}
         title="Back to DankeSuper">
        <svg width="22" height="22" viewBox="0 0 22 22">
          <circle cx="11" cy="11" r="9" fill="none" stroke="#bcd397" strokeWidth="1.2" />
          <circle cx="11" cy="11" r="4" fill="#bcd397" fillOpacity="0.7" />
          <circle cx="11" cy="11" r="1.5" fill="#0d100a" />
        </svg>
        <div>
          <div style={{ fontFamily: '"JetBrains Mono", monospace', fontSize: '9px', letterSpacing: '0.22em',
                        color: 'rgba(232,228,213,0.4)', textTransform: 'uppercase', marginBottom: '2px' }}>
            ← DANKESUPER
          </div>
          <div className="wordmark">PHYTONOMY</div>
          <div className="tagline">A terpene atlas of cannabis</div>
        </div>
      </a>

      <nav className="modes">
        {[
          { id: 'atlas',  label: 'Atlas',   sub: 'phytochemical map'  },
          { id: 'strain', label: 'Strain',  sub: 'fingerprint detail' },
          { id: 'blend',  label: 'Blend',   sub: 'compose & match'    },
        ].map(m => (
          <button key={m.id}
            className={`mode-btn ${mode === m.id ? 'active' : ''}`}
            onClick={() => setMode(m.id)}>
            <span className="ml">{m.label}</span>
            <span className="ms">{m.sub}</span>
          </button>
        ))}
      </nav>

      <div className="meta" title={dataError ? `Supabase fetch failed: ${dataError}` : ''}>
        <span className="num">{liveCount.toLocaleString()}</span>
        <span className="lab">{isLive ? 'live cultivars' : 'demo cultivars'}</span>
        <span className="sep">/</span>
        <span className="num">89,923</span>
        <span className="lab">lab samples</span>
        <span className="sep">/</span>
        <span className="num">11</span>
        <span className="lab">terpenes</span>
        {dataError && (
          <span style={{ color: '#f08654', marginLeft: 8, fontSize: 9, letterSpacing: '0.14em' }}>
            ⚠ FALLBACK
          </span>
        )}
      </div>

      <button className="hdr-mobile-btn detail-btn" aria-label="Strain detail"
        onClick={() => setDrawer('detail')} disabled={!hasSelection}>
        Detail
      </button>
    </header>
  );
}

// ============================================================
function LeftRail({
  query, setQuery, categoryFilter, setCategoryFilter,
  colorMode, setColorMode, similarityThreshold, setSimThreshold,
  searchResults, selected, onSelectStrain, mode, drawerOpen, closeDrawer,
}) {
  return (
    <aside className={`rail rail-l ${drawerOpen ? 'open' : ''}`}>
      <button className="drawer-close" aria-label="Close" onClick={closeDrawer}>×</button>
      <Section title="Search">
        <input
          className="search"
          placeholder="OG Kush, Sativa, citrus…"
          value={query}
          onChange={e => setQuery(e.target.value)}
        />
      </Section>

      <Section title="Category">
        <div className="seg">
          {['All', 'Indica', 'Hybrid', 'Sativa'].map(c => (
            <button key={c}
              onClick={() => setCategoryFilter(c)}
              className={categoryFilter === c ? 'active' : ''}
              style={c !== 'All' && categoryFilter === c ? {
                color: CATEGORY_COLORS[c], borderColor: CATEGORY_COLORS[c] + '70',
                background: CATEGORY_COLORS[c] + '14',
              } : {}}>
              {c}
            </button>
          ))}
        </div>
      </Section>

      {mode === 'atlas' && (
        <Section title={`Similarity ≥ ${similarityThreshold.toFixed(2)}`}>
          <input type="range" min="0.7" max="0.97" step="0.01"
            value={similarityThreshold}
            onChange={e => setSimThreshold(parseFloat(e.target.value))}
            className="range" />
          <div className="range-foot">
            <span>loose</span><span>strict</span>
          </div>
        </Section>
      )}

      {mode === 'atlas' && (
        <Section title="Color nodes by">
          {[
            { id: 'category', label: 'Category' },
            { id: 'dom',      label: 'Dominant terpene' },
            { id: 'thc',      label: 'THC concentration' },
          ].map(o => (
            <button key={o.id}
              className={`stack-btn ${colorMode === o.id ? 'active' : ''}`}
              onClick={() => setColorMode(o.id)}>
              {o.label}
            </button>
          ))}
        </Section>
      )}

      <Section title={`${searchResults.length} cultivars`} pad>
        <div className="strain-list">
          {searchResults.slice(0, 50).map(s => (
            <StrainCard key={s.slug}
              strain={s}
              active={selected === s.slug}
              onClick={() => onSelectStrain(s)} />
          ))}
        </div>
      </Section>
    </aside>
  );
}

function Section({ title, children, pad }) {
  return (
    <div className={`section ${pad ? 'pad' : ''}`}>
      <div className="kicker">{title}</div>
      <div className="body">{children}</div>
    </div>
  );
}

// ============================================================
function AtlasView({ selected, setSelected, setHovered, similarityThreshold, categoryFilter, colorMode, hovered, sel, dataTick }) {
  return (
    <div className="atlas-view">
      <div className="canvas-head">
        <h1>Phytochemical <em>atlas</em></h1>
        <p>
          Each cultivar is projected by its <em>terpene fingerprint</em> onto two principal components.
          Strains drift toward four corners — myrcene-heavy indicas at upper left, terpinolene sativas
          at right, caryophyllene-limonene hybrids at lower middle. Lines connect cultivars whose
          profiles share <span className="mono">≥ {similarityThreshold.toFixed(2)}</span> cosine similarity.
        </p>
      </div>

      <div className="atlas-stage">
        <AtlasMap
          key={`atlas-${dataTick}`}
          selected={selected}
          onSelect={setSelected}
          onHover={setHovered}
          similarityThreshold={similarityThreshold}
          categoryFilter={categoryFilter}
          colorMode={colorMode}
        />
      </div>

      <Legend colorMode={colorMode} />
    </div>
  );
}

function Legend({ colorMode }) {
  return (
    <div className="legend">
      {colorMode === 'category' && (['Indica', 'Hybrid', 'Sativa']).map(c => (
        <div key={c} className="leg-item">
          <span className="leg-dot" style={{ background: CATEGORY_COLORS[c] }}></span>
          <span>{c}</span>
        </div>
      ))}
      {colorMode === 'dom' && TERPENE_KEYS.map(k => (
        <div key={k} className="leg-item">
          <span className="leg-dot" style={{ background: TERPENE_META[k].color }}></span>
          <span>{TERPENE_META[k].label}</span>
        </div>
      ))}
      {colorMode === 'thc' && [
        { t: 16, l: '16% THC' }, { t: 19, l: '19%' }, { t: 22, l: '22%' }, { t: 24, l: '24%+' }
      ].map(x => {
        const t = clamp((x.t - 16) / 8, 0, 1);
        const hue = 110 - t * 80;
        return (
          <div key={x.t} className="leg-item">
            <span className="leg-dot" style={{ background: `oklch(0.72 0.14 ${hue})` }}></span>
            <span>{x.l}</span>
          </div>
        );
      })}
    </div>
  );
}

// ============================================================
function StrainView({ sel, similar, onPick }) {
  if (!sel) return <EmptyState />;
  const meta = TERPENE_META[sel.dom] || TERPENE_META.myrcene;

  const sortedTerps = TERPENE_KEYS
    .map(k => ({ k, v: sel[k] ?? 0 }))
    .sort((a, b) => b.v - a.v);

  const popPct = sel.popularity != null ? (sel.popularity * 100).toFixed(0) : '—';

  return (
    <div className="strain-view">
      <div className="canvas-head">
        <div className="strain-title">
          <span className="kicker num">{popPct} / 100 prevalence · n = {sel.n_samples ?? '—'} samples</span>
          <h1>
            <em>{sel.name}</em>
          </h1>
          <p>
            <span style={{ color: CATEGORY_COLORS[sel.cat] }}>{sel.cat}</span> ·{' '}
            <span style={{ color: meta.color }}>{meta.label}-dominant</span> ·{' '}
            <span>{sel.chemotype}</span> ·{' '}
            <span className="mono">{(sel.tot_thc ?? 0).toFixed(1)}% THC</span>
          </p>
        </div>
      </div>

      <div className="strain-body">
        <div className="strain-radar-block">
          <TerpeneRadar strain={sel} size={360} />
          <div className="aroma-block">
            <div className="kicker">Aroma signature</div>
            <div className="aroma-text" style={{ color: meta.color }}>
              {meta.aroma}
            </div>
            <div className="kicker mt">Reported effects</div>
            <div className="effect-text">{meta.effect}</div>
          </div>
        </div>

        <div className="strain-bars">
          <div className="kicker">Terpene breakdown · % dry weight</div>
          {sortedTerps.map(({ k, v }) => {
            const m = TERPENE_META[k];
            const pct = Math.min(100, (v / TERPENE_P95[k]) * 100);
            return (
              <div key={k} className="bar-row">
                <div className="bar-head">
                  <span className="bar-name" style={{ color: m.color }}>
                    <span className="bar-short">{m.short}</span> {m.label}
                  </span>
                  <span className="bar-val">{v.toFixed(3)}</span>
                </div>
                <div className="bar-track">
                  <div className="bar-fill" style={{ width: `${pct}%`, background: m.color }}></div>
                </div>
                <div className="bar-sub">{m.aroma}</div>
              </div>
            );
          })}
        </div>

        <div className="strain-cannabinoids">
          <div className="kicker">Cannabinoid profile</div>
          {[
            { l: 'THC',  v: sel.tot_thc ?? 0,  max: 30, c: '#f0c96a' },
            { l: 'CBD',  v: sel.tot_cbd ?? 0,  max: 1,  c: '#bcd397' },
            { l: 'CBG',  v: sel.tot_cbg ?? 0,  max: 1,  c: '#9ae0c2' },
          ].map(b => (
            <div key={b.l} className="cann-row">
              <span className="cann-l" style={{ color: b.c }}>{b.l}</span>
              <div className="cann-track">
                <div className="cann-fill"
                  style={{ width: `${clamp(b.v / b.max, 0, 1) * 100}%`, background: b.c }}></div>
              </div>
              <span className="cann-v">{b.v.toFixed(2)}%</span>
            </div>
          ))}

          <div className="kicker mt">Sample provenance</div>
          <div className="prov">
            <div><span className="lab">Source</span><span className="val">Pestele et al. 2022</span></div>
            <div><span className="lab">Method</span><span className="val">GC-FID, HPLC</span></div>
            <div><span className="lab">Samples</span><span className="val">{sel.n_samples ?? '—'}</span></div>
            <div><span className="lab">Aggregated</span><span className="val">median</span></div>
          </div>
        </div>
      </div>

      <div className="strain-neighbors">
        <div className="kicker">Phytochemical neighbors · {similar.length} cultivars within cosine 0.70</div>
        <div className="neighbor-grid">
          {similar.map(({ strain, sim }) => (
            <StrainCard key={strain.slug} strain={strain} similarity={sim} onClick={() => onPick(strain)} />
          ))}
        </div>
      </div>
    </div>
  );
}

function EmptyState() {
  return (
    <div className="empty">
      <div className="kicker">No strain selected</div>
      <p>Choose a cultivar from the list or pick a node on the atlas.</p>
    </div>
  );
}

// ============================================================
function BlendView({ values, setValues, onSelect, selected, blendPCA, similarityThreshold, categoryFilter, colorMode, sel, dataTick }) {
  return (
    <div className="blend-view">
      <div className="canvas-head">
        <h1>Compose a <em>blend</em></h1>
        <p>
          Dial each of the eleven terpenes. The atlas updates in real time to show where your
          imagined cultivar would land in chemical space, and the right column proposes existing
          strains nearest your composition by cosine similarity on the L2-normalized vector.
        </p>
      </div>
      <div className="blend-stage">
        <div className="blend-atlas">
          <AtlasMap
            key={`blend-atlas-${dataTick}`}
            selected={selected}
            onSelect={onSelect}
            similarityThreshold={similarityThreshold}
            categoryFilter={categoryFilter}
            colorMode={colorMode}
            blendPoint={blendPCA}
          />
        </div>
        <BlendBuilder
          values={values}
          setValues={setValues}
          onSelect={onSelect}
          selected={selected} />
      </div>
    </div>
  );
}

// ============================================================
function RightRail({ sel, similar, onPick, drawerOpen, closeDrawer }) {
  if (!sel) return (
    <aside className={`rail rail-r ${drawerOpen ? 'open' : ''}`}>
      <button className="drawer-close" aria-label="Close" onClick={closeDrawer}>×</button>
      <div className="empty-rail">
        <div className="kicker">Detail panel</div>
        <p>Select a cultivar to see its terpene radar, cannabinoid load, and nearest neighbors.</p>
      </div>
    </aside>
  );

  const meta = TERPENE_META[sel.dom] || TERPENE_META.myrcene;
  const top3 = TERPENE_KEYS
    .map(k => ({ k, v: sel[k] ?? 0 }))
    .sort((a, b) => b.v - a.v).slice(0, 3);

  return (
    <aside className={`rail rail-r ${drawerOpen ? 'open' : ''}`}>
      <button className="drawer-close" aria-label="Close" onClick={closeDrawer}>×</button>
      <div className="rr-head">
        <span className="kicker">Selected</span>
        <h2><em>{sel.name}</em></h2>
        <div className="rr-tags">
          <span className="tag" style={{
            color: CATEGORY_COLORS[sel.cat],
            borderColor: CATEGORY_COLORS[sel.cat] + '60',
            background: CATEGORY_COLORS[sel.cat] + '14',
          }}>{sel.cat}</span>
          <span className="tag" style={{
            color: meta.color, borderColor: meta.color + '60', background: meta.color + '14',
          }}>{meta.short} dom</span>
          <span className="tag mono">{(sel.tot_thc ?? 0).toFixed(1)}% THC</span>
        </div>
      </div>

      <div className="rr-radar">
        <TerpeneRadar strain={sel} size={260} />
      </div>

      <div className="rr-section">
        <div className="kicker">Top three terpenes</div>
        {top3.map(({ k, v }) => {
          const m = TERPENE_META[k];
          const pct = Math.min(100, (v / TERPENE_P95[k]) * 100);
          return (
            <div key={k} className="rr-bar">
              <div className="rr-bar-head">
                <span style={{ color: m.color }}>{m.label}</span>
                <span className="mono dim">{v.toFixed(3)}%</span>
              </div>
              <div className="bar-track">
                <div className="bar-fill" style={{ width: `${pct}%`, background: m.color }} />
              </div>
            </div>
          );
        })}
      </div>

      <div className="rr-section">
        <div className="kicker">Nearest neighbors · top 5</div>
        <div className="rr-neighbors">
          {similar.slice(0, 5).map(({ strain, sim }) => (
            <StrainCard key={strain.slug} strain={strain} similarity={sim} onClick={() => onPick(strain)} />
          ))}
        </div>
      </div>
    </aside>
  );
}

// ============================================================
function Footer() {
  return (
    <footer className="footer">
      <span>Pestele et al. (2022) · PLoS ONE · PMID 35576208</span>
      <span className="dim">/ similarity = cosine on L2-normalized 11-dim terpene vector</span>
      <span className="dim">/ PCA eigenvectors derived offline (sklearn)</span>
      <span className="dim">/ Category labels are commercial designations, not genetic.</span>
    </footer>
  );
}

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