// surgx-browse-v3.jsx — SurgX Browse / dictionary index, design v3.
// Mounts #root; exposes window.SurgxBrowseV3Root.
// Blank-over-wrong throughout: every honest-empty is designed, never fabricated.
// Reuses the .sxv3 / .sx token root and component classes from surgx-pdp-v3.css.
(() => {
const { useState, useEffect, useMemo, useRef } = React;
const SX_SPECIALTY_FORK = true;

/* ---- shared search core (surgx-search-core.js, loaded before this script) ---- */
const { str, normalizeVendor, matchesQuery } = window.SurgxSearchCore;

/* ---- tiny helpers (mirror pattern from pdp-v3) ---- */
const initials = n => { const w = String(n || '?').split(/\s+/).filter(Boolean); return ((w[0]?.[0] || '?') + (w[1]?.[0] || '')).toUpperCase(); };
const Icon = ({ d, size = 14 }) => (
  <svg width={size} height={size} viewBox="0 0 16 16" fill="none" aria-hidden="true">
    <path d={d} stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
  </svg>
);
const ICON = { back: 'M10 13 5 8l5-5', chevDown: 'M4 6l4 4 4-4' };

const Glyph = ({ name, size = 40 }) => (
  <div className="sx-glyph" style={{ width: size, height: size, borderRadius: 10, fontSize: size * 0.34, flex: 'none' }}>
    {initials(name)}
  </div>
);

const Empty = ({ title, detail }) => (
  <div className="sx-empty"><div className="t">{title}</div>{detail ? <div className="d">{detail}</div> : null}</div>
);

/* ---- clearance chip (same 3-state vocabulary as pdp-v3) ---- */
function ClearanceChip({ fda_510k_primary }) {
  const k = (str(fda_510k_primary).match(/^(K\d{6})$/) || [])[1];
  if (k) return <span className="sx-clearance cited">FDA 510(k) {k}</span>;
  return <span className="sx-clearance none">no K-number on record</span>;
}

/* ---- derive top-20 vendors by count from curated systems list ---- */
function topVendors(systems) {
  const counts = {};
  for (const s of systems) {
    const v = normalizeVendor(s.vendor);
    if (v) counts[v] = (counts[v] || 0) + 1;
  }
  return Object.entries(counts)
    .sort((a, b) => b[1] - a[1])
    .slice(0, 20)
    .map(([name, count]) => ({ name, count }));
}

/* ---- Facet panel (desktop rail list or mobile chip row) ---- */
function FacetPanel({ label, items, value, onChange }) {
  return (
    <div className="bx-facet">
      <div className="bx-facet-head">{label}</div>
      <div className="bx-facet-list">
        {items.map(item => (
          <button
            key={item.value}
            className={'bx-facet-item' + (value === item.value ? ' is-on' : '')}
            onClick={() => onChange(value === item.value ? null : item.value)}
          >
            <span className="fi-label">{item.label}</span>
            {item.count != null ? <span className="fi-count">{item.count}</span> : null}
          </button>
        ))}
      </div>
    </div>
  );
}

/* ---- single browse card ---- */
function BrowseCard({ slug, display_name, vendor, subspecialty, fda_510k_primary, tier }) {
  const name = display_name || slug;
  const vendorShort = normalizeVendor(vendor);
  const subspec = str(subspecialty).slice(0, 60);
  return (
    <a
      href={'/system/' + encodeURIComponent(slug)}
      className="sx-card bx-card"
    >
      <Glyph name={name} size={40} />
      <div className="bx-card-body">
        <div className="bx-card-name">{name}</div>
        {vendorShort ? <div className="bx-card-vendor">{vendorShort}</div> : null}
        {subspec ? <div className="bx-card-subspec">{subspec}</div> : null}
        <div className="bx-card-chips">
          {tier === 'curated'
            ? <span className="sx-tier curated">curated · grounded</span>
            : <span className="sx-tier generic">FDA records only</span>}
          <ClearanceChip fda_510k_primary={fda_510k_primary} />
        </div>
      </div>
    </a>
  );
}

function CompactRow({ slug, display_name, vendor, subspecialty, fda_510k_primary, tier }) {
  const name = display_name || slug;
  const vendorShort = normalizeVendor(vendor);
  const subspec = str(subspecialty).slice(0, 72);
  return (
    <a href={'/system/' + encodeURIComponent(slug)} className="bx-compact-row">
      <Glyph name={name} size={32} />
      <div className="bcr-main">
        <div className="bcr-name">{name}</div>
        <div className="bcr-meta">
          {vendorShort ? <span>{vendorShort}</span> : null}
          {subspec ? <span>{subspec}</span> : null}
        </div>
      </div>
      <div className="bcr-side">
        {tier === 'generic' ? <span className="sx-tier generic">FDA records only</span> : null}
        <ClearanceChip fda_510k_primary={fda_510k_primary} />
      </div>
    </a>
  );
}

function GroupedSection({ node, onOpen }) {
  const ref = useRef(null);
  const [loaded, setLoaded] = useState(false);
  const [state, setState] = useState({ loading: false, error: null, data: null });

  useEffect(() => {
    const el = ref.current;
    if (!el || loaded) return;
    const load = () => {
      setLoaded(true);
      setState({ loading: true, error: null, data: null });
      const qs = new URLSearchParams({ specialty: node.slug, page: '1', page_size: '6' });
      fetch('/api/r2/systems?' + qs.toString())
        .then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
        .then(data => setState({ loading: false, error: null, data }))
        .catch(e => setState({ loading: false, error: e.message, data: null }));
    };
    if (!('IntersectionObserver' in window)) {
      load();
      return;
    }
    const io = new IntersectionObserver(entries => {
      if (entries.some(e => e.isIntersecting)) {
        io.disconnect();
        load();
      }
    }, { rootMargin: '420px 0px' });
    io.observe(el);
    return () => io.disconnect();
  }, [loaded, node.slug]);

  const items = state.data ? (state.data.items || []) : [];
  const remaining = Math.max(0, node.count - items.length);
  return (
    <section className="bx-group-section" ref={ref} aria-labelledby={'section-' + node.slug}>
      <div className="bx-section-head">
        <div>
          <h2 id={'section-' + node.slug}>{node.display}</h2>
          {node.has_children ? <div className="bx-section-sub">subspecialty tiers available</div> : null}
        </div>
        <span className="bx-count-badge">{node.count}</span>
      </div>
      {state.loading ? (
        <div className="bx-section-loading">loading section…</div>
      ) : state.error ? (
        <Empty title="section unavailable" detail={state.error} />
      ) : items.length ? (
        <div className="bx-compact-list">
          {items.map(s => <CompactRow key={s.slug} {...s} tier="curated" />)}
        </div>
      ) : loaded ? (
        <Empty title="no systems in section" detail="The count source and leaf source disagree." />
      ) : (
        <div className="bx-section-loading">ready to load…</div>
      )}
      {Array.isArray(node.children) && node.children.length ? (
        <div className="bx-child-nodes">
          {node.children.map(child => (
            <button key={child.slug} type="button" className="bx-child-node" onClick={() => onOpen(child.slug)}>
              <span>{child.display}</span>
              <span>{child.count}</span>
            </button>
          ))}
        </div>
      ) : null}
      {items.length ? (
        <div className="bx-section-foot">
          <button className="sx-btn quiet sm" type="button" onClick={() => onOpen(node.slug)}>
            {remaining > 0 ? '… +' + remaining + ' more  → ' : ''}View all {node.count} in {node.display}
          </button>
        </div>
      ) : null}
    </section>
  );
}

function PagedRows({ endpoint, emptyTitle }) {
  const [page, setPage] = useState(1);
  const [state, setState] = useState({ loading: true, error: null, data: null });

  useEffect(() => {
    setPage(1);
  }, [endpoint]);

  useEffect(() => {
    const sep = endpoint.indexOf('?') === -1 ? '?' : '&';
    fetch(endpoint + sep + 'page=' + page + '&page_size=24')
      .then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
      .then(data => setState(prev => ({
        loading: false,
        error: null,
        data: page === 1 ? data : { ...data, items: [...((prev.data && prev.data.items) || []), ...(data.items || [])] },
      })))
      .catch(e => setState({ loading: false, error: e.message, data: null }));
  }, [endpoint, page]);

  if (state.loading && page === 1) return <div className="bx-section-loading">loading systems…</div>;
  if (state.error) return <Empty title="systems unavailable" detail={state.error} />;
  const data = state.data || { items: [], total: 0, has_more: false };
  const items = data.items || [];
  if (!items.length) return <Empty title={emptyTitle} detail="No records match the current filters." />;
  return (
    <>
      <div className="bx-count-bar">{items.length} of {data.total} systems</div>
      <div className="bx-compact-list">
        {items.map(s => <CompactRow key={(s.tier || 'curated') + '-' + s.slug} {...s} tier={s.tier || 'curated'} />)}
      </div>
      {data.has_more ? (
        <div className="bx-show-more">
          <button className="sx-btn quiet sm" type="button" onClick={() => setPage(p => p + 1)}>show more</button>
        </div>
      ) : null}
    </>
  );
}

function specialtySystemCount(node) {
  return Number(node && node.count) || 0;
}

function vendorRowsFromSystems(systems) {
  const counts = {};
  for (const s of systems || []) {
    const v = normalizeVendor(s.vendor);
    if (v) counts[v] = (counts[v] || 0) + 1;
  }
  return Object.entries(counts)
    .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
    .map(([name, count]) => ({ name, count }));
}

function fetchAllSpecialtySystems(slug) {
  const items = [];
  let page = 1;
  const MAX_PAGES = 60; // hard cap (60*100=6000 systems, far above any specialty) — runaway guard
  const loadPage = () => {
    const qs = new URLSearchParams({
      tier: 'curated',
      specialty: slug,
      page: String(page),
      page_size: '100',
    });
    return fetch('/api/r2/systems?' + qs.toString())
      .then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
      .then(data => {
        const got = data.items || [];
        items.push(...got);
        // Termination guards (M3 review): stop on no-more, on an empty page
        // (zero progress — defends a backend has_more:true bug from infinite-looping),
        // or at the page cap. page_size=100 is the backend clamp.
        if (data.has_more && got.length > 0 && page < MAX_PAGES) {
          page += 1;
          return loadPage();
        }
        return items;
      });
  };
  return loadPage();
}

function SpecialtyMoreWaysIn() {
  return (
    <div style={{ marginTop: 18 }}>
      <div className="sx-kicker">MORE WAYS IN</div>
      <div className="bx-compact-list" style={{ marginTop: 8 }}>
        <div className="bx-compact-row">
          <Glyph name="Supplies" size={32} />
          <div className="bcr-main">
            <div className="bcr-name">Supplies & item categories</div>
            <div className="bcr-meta"><span>cross-catalog</span><span>coming soon</span></div>
          </div>
        </div>
        <div className="bx-compact-row">
          <Glyph name="Procedure" size={32} />
          <div className="bcr-main">
            <div className="bcr-name">Procedure search</div>
            <div className="bcr-meta"><span>mapping in progress</span><span>coming soon</span></div>
          </div>
        </div>
      </div>
    </div>
  );
}

function SpecialtyFork({ specialty, node, systemsState, onPickVendor, onPickSearch, onBack }) {
  const systemCount = specialtySystemCount(node);
  const vendorCount = systemsState && systemsState.items ? vendorRowsFromSystems(systemsState.items).length : null;
  const vendorBadge = vendorCount == null ? '...' : vendorCount.toLocaleString();
  return (
    <>
      <div className="bx-view-head">
        <button type="button" className="sx-btn quiet sm" onClick={onBack}>
          <Icon d={ICON.back} size={13} /> all specialties
        </button>
        <h1>{specialty}</h1>
      </div>
      <div className="sx-kicker" style={{ display: 'flex', justifyContent: 'space-between', gap: 12 }}>
        <span>BROWSE THIS SPECIALTY</span>
        <span>{systemCount.toLocaleString()} curated</span>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 12, marginTop: 10 }}>
        <button
          type="button"
          className="sx-card"
          onClick={onPickVendor}
          style={{ minHeight: 64, textAlign: 'left', display: 'flex', alignItems: 'center', gap: 12, borderLeft: '3px solid var(--accent-600)' }}
        >
          <div className="bx-card-body">
            <div className="bx-card-name">By vendor</div>
            <div className="bx-card-vendor">Makers active in this specialty</div>
          </div>
          <span className="bx-count-badge">{vendorBadge}</span>
        </button>
        <button
          type="button"
          className="sx-card"
          onClick={onPickSearch}
          style={{ minHeight: 64, textAlign: 'left', display: 'flex', alignItems: 'center', gap: 12, borderLeft: '3px solid var(--accent-600)' }}
        >
          <div className="bx-card-body">
            <div className="bx-card-name">Search systems</div>
            <div className="bx-card-vendor">Find a system within this specialty</div>
          </div>
          <span className="bx-count-badge">{systemCount.toLocaleString()}</span>
        </button>
      </div>
      {systemsState && systemsState.error ? (
        <div style={{ marginTop: 12 }}>
          <Empty title="vendor count unavailable" detail={systemsState.error} />
        </div>
      ) : null}
      <SpecialtyMoreWaysIn />
      <div className="bx-count-bar" style={{ marginTop: 12 }}>both lead cards reach the same curated systems — pick your entry axis.</div>
    </>
  );
}

function SpecialtyVendorBranch({ specialty, systemsState, onBack }) {
  const [vendorFilter, setVendorFilter] = useState(null);
  const systems = systemsState && systemsState.items ? systemsState.items : [];
  const vendors = useMemo(() => vendorRowsFromSystems(systems), [systems]);
  const vendorSystems = useMemo(() => (
    vendorFilter ? systems.filter(s => normalizeVendor(s.vendor) === vendorFilter) : []
  ), [systems, vendorFilter]);

  return (
    <>
      <div className="bx-view-head">
        <button type="button" className="sx-btn quiet sm" onClick={onBack}>
          <Icon d={ICON.back} size={13} /> {specialty}
        </button>
        <h1>{vendorFilter || ('Vendors — ' + specialty)}</h1>
      </div>
      {systemsState && systemsState.loading ? (
        <div className="bx-section-loading">loading vendors...</div>
      ) : systemsState && systemsState.error ? (
        <Empty title="vendors unavailable" detail={systemsState.error} />
      ) : vendorFilter ? (
        <>
          <div className="bx-view-head">
            <button type="button" className="sx-btn quiet sm" onClick={() => setVendorFilter(null)}>
              <Icon d={ICON.back} size={13} /> vendors
            </button>
            <div className="bx-count-bar">{vendorSystems.length} systems</div>
          </div>
          <div className="bx-compact-list">
            {vendorSystems.map(s => <CompactRow key={(s.tier || 'curated') + '-' + s.slug} {...s} tier={s.tier || 'curated'} />)}
          </div>
        </>
      ) : vendors.length ? (
        <div className="bx-compact-list">
          {vendors.map(v => (
            <button
              key={v.name}
              type="button"
              className="bx-compact-row"
              onClick={() => setVendorFilter(v.name)}
              style={{ width: '100%', textAlign: 'left' }}
            >
              <Glyph name={v.name} size={32} />
              <div className="bcr-main">
                <div className="bcr-name">{v.name}</div>
                <div className="bcr-meta"><span>system counts</span></div>
              </div>
              <span className="bx-count-badge">{v.count}</span>
            </button>
          ))}
        </div>
      ) : (
        <Empty title="no vendors on record" detail="No normalized vendor names are available for this specialty." />
      )}
    </>
  );
}

function SpecialtySearchBranch({ specialty, node, onBack }) {
  const [q, setQ] = useState('');
  const [debouncedQ, setDebouncedQ] = useState('');
  const slug = node && node.slug ? node.slug : '';

  useEffect(() => {
    const t = setTimeout(() => setDebouncedQ(q.trim()), 200);
    return () => clearTimeout(t);
  }, [q]);

  const endpoint = useMemo(() => {
    const params = new URLSearchParams({ tier: 'curated', specialty: slug });
    if (debouncedQ) params.set('q', debouncedQ);
    return '/api/r2/systems?' + params.toString();
  }, [slug, debouncedQ]);

  return (
    <>
      <div className="bx-view-head">
        <button type="button" className="sx-btn quiet sm" onClick={onBack}>
          <Icon d={ICON.back} size={13} /> {specialty}
        </button>
        <h1>Search systems</h1>
      </div>
      <input
        className="bx-search"
        type="search"
        value={q}
        onChange={e => setQ(e.target.value)}
        placeholder="Search by system name, K-number, or vendor"
        aria-label="Search by system name, K-number, or vendor"
        style={{ width: '100%', marginBottom: 12 }}
      />
      <PagedRows key={endpoint} endpoint={endpoint} emptyTitle="no systems in specialty" />
    </>
  );
}

function GroupedBrowseV3Root({ initialSearch, initialSpecialty, initialTier }) {
  const [tree, setTree] = useState(null);
  const [treeErr, setTreeErr] = useState(null);
  const [search, setSearch] = useState(initialSearch || '');
  const [specialtyFilter, setSpecialtyFilter] = useState(initialSpecialty || null);
  const [branchMode, setBranchMode] = useState(null);
  const [specialtySystemsState, setSpecialtySystemsState] = useState({ slug: null, loading: false, error: null, items: null });
  const tierFilter = initialTier === 'generic' ? 'generic' : 'curated';

  useEffect(() => {
    fetch('/api/r2/taxonomy/tree?tier=curated')
      .then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
      .then(setTree)
      .catch(e => setTreeErr(e.message));
  }, []);

  const nodes = useMemo(() => tree && tree.nodes ? tree.nodes : [], [tree]);
  const specialtyItems = useMemo(() => [
    { value: '__all__', label: 'All systems', count: null },
    ...nodes.map(n => ({ value: n.slug, label: n.display, count: n.count })),
  ], [nodes]);
  const currentNode = nodes.find(n => n.slug === specialtyFilter);
  const specialtyNode = currentNode || (specialtyFilter ? { slug: specialtyFilter, display: specialtyFilter, count: 0 } : null);
  const currentSpecialty = specialtyNode ? specialtyNode.display : specialtyFilter;
  const activeSearch = search.trim();
  const searchEndpoint = '/api/r2/search?' + new URLSearchParams({ q: activeSearch, tier: tierFilter }).toString();
  const specialtyEndpoint = '/api/r2/systems?' + new URLSearchParams({ tier: 'curated', specialty: specialtyFilter || '' }).toString();
  const clearSpecialty = () => {
    setSpecialtyFilter(null);
    setBranchMode(null);
  };
  const openSpecialty = slug => {
    setSpecialtyFilter(slug);
    setBranchMode(null);
  };

  useEffect(() => {
    if (!SX_SPECIALTY_FORK || !specialtyFilter) return;
    let cancelled = false;
    setSpecialtySystemsState(prev => ({
      slug: specialtyFilter,
      loading: true,
      error: null,
      items: prev.slug === specialtyFilter ? prev.items : null,
    }));
    fetchAllSpecialtySystems(specialtyFilter)
      .then(items => {
        if (!cancelled) setSpecialtySystemsState({ slug: specialtyFilter, loading: false, error: null, items });
      })
      .catch(e => {
        if (!cancelled) setSpecialtySystemsState({ slug: specialtyFilter, loading: false, error: e.message, items: null });
      });
    return () => { cancelled = true; };
  }, [specialtyFilter]);

  return (
    <div className="sxv3 sx sx-browse bx-grouped">
      <div className="bx-page">
        <div className="bx-topbar">
          <span className="bx-logo"><span className="bx-logo-mark" />SurgX</span>
          <span className="bx-tagline">surgical supply, with receipts</span>
          <input
            className="bx-search"
            type="search"
            value={search}
            onChange={e => setSearch(e.target.value)}
            placeholder="Search systems, vendors, K-numbers…"
            aria-label="Search systems"
          />
        </div>

        {treeErr ? <Empty title="taxonomy unavailable" detail={treeErr} /> : null}

        <div className="bx-layout">
          <div className="bx-facet-rail">
            {tree ? (
              <FacetPanel
                label="Specialty"
                items={specialtyItems}
                value={specialtyFilter || '__all__'}
                onChange={v => v === '__all__' ? clearSpecialty() : openSpecialty(v)}
              />
            ) : null}
          </div>

          <main className="bx-main">
            {activeSearch ? (
              <>
                <div className="bx-view-head">
                  <button type="button" className="sx-btn quiet sm" onClick={() => setSearch('')}>
                    <Icon d={ICON.back} size={13} /> clear search
                  </button>
                  <div className="bx-count-bar">relevance-ranked results</div>
                </div>
                <PagedRows key={searchEndpoint} endpoint={searchEndpoint} emptyTitle="no search matches" />
              </>
            ) : SX_SPECIALTY_FORK && specialtyFilter && branchMode === null ? (
              <SpecialtyFork
                specialty={currentSpecialty}
                node={specialtyNode}
                systemsState={specialtySystemsState}
                onPickVendor={() => setBranchMode('vendor')}
                onPickSearch={() => setBranchMode('search')}
                onBack={clearSpecialty}
              />
            ) : SX_SPECIALTY_FORK && specialtyFilter && branchMode === 'vendor' ? (
              <SpecialtyVendorBranch
                specialty={currentSpecialty}
                node={specialtyNode}
                systemsState={specialtySystemsState}
                onBack={() => setBranchMode(null)}
              />
            ) : SX_SPECIALTY_FORK && specialtyFilter && branchMode === 'search' ? (
              <SpecialtySearchBranch
                specialty={currentSpecialty}
                node={specialtyNode}
                onBack={() => setBranchMode(null)}
              />
            ) : specialtyFilter ? (
              <>
                <div className="bx-view-head">
                  <button type="button" className="sx-btn quiet sm" onClick={clearSpecialty}>
                    <Icon d={ICON.back} size={13} /> all specialties
                  </button>
                  <h1>{currentNode ? currentNode.display : specialtyFilter}</h1>
                </div>
                <PagedRows key={specialtyEndpoint} endpoint={specialtyEndpoint} emptyTitle="no systems in specialty" />
              </>
            ) : tree ? (
              <>
                <div className="bx-count-bar">
                  {tree.tiers.curated.toLocaleString()} curated systems · {nodes.length} specialties
                </div>
                {nodes.map(node => (
                  <GroupedSection key={node.slug} node={node} onOpen={openSpecialty} />
                ))}
              </>
            ) : (
              <div className="bx-section-loading">loading topology…</div>
            )}
          </main>
        </div>
      </div>
    </div>
  );
}

/* ---- root component ---- */
function SurgxBrowseV3Root() {
  /* URL param seeding — initial state, not a useEffect (avoids unfiltered flash) */
  const _params = new URLSearchParams(location.search);
  const _tierParam = _params.get('tier');
  /* ACTIVATED 2026-06-11: grouped topology is the default. Rollback hatch: ?group=0
     forces the legacy flat list (revert this one line — flip back to === '1' — to dark again). */
  if (_params.get('group') !== '0') {
    return (
      <GroupedBrowseV3Root
        initialSearch={_params.get('q') || ''}
        initialSpecialty={_params.get('specialty') || null}
        initialTier={_tierParam === 'generic' ? 'generic' : 'curated'}
      />
    );
  }

  /* fetch state */
  const [taxonomy, setTaxonomy] = useState(null);
  const [systems, setSystems] = useState(null);
  const [genericIndex, setGenericIndex] = useState(null); // null = loading, false = failed/404
  const [taxErr, setTaxErr] = useState(null);
  const [sysErr, setSysErr] = useState(null);

  /* filter/UI state */
  const [search, setSearch] = useState(_params.get('q') || '');
  const [specialtyFilter, setSpecialtyFilter] = useState(_params.get('specialty') || null);
  const [vendorFilter, setVendorFilter] = useState(null);
  /* ?tier= deep-link (landing receipts cards); only the two known values seed */
  const [tierFilter, setTierFilter] = useState(
    _tierParam === 'curated' || _tierParam === 'generic' ? _tierParam : null
  ); // null=all, 'curated', 'generic'
  const [curatedPage, setCuratedPage] = useState(1);
  const [genericPage, setGenericPage] = useState(1);
  const [genericOpen, setGenericOpen] = useState(false);

  /* parallel fetch on mount */
  useEffect(() => {
    fetch('/api/r2/taxonomy')
      .then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
      .then(setTaxonomy)
      .catch(e => setTaxErr(e.message));

    fetch('/api/r2/systems')
      .then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
      .then(setSystems)
      .catch(e => setSysErr(e.message));

    fetch('/api/r2/generic-index')
      .then(r => { if (!r.ok) return false; return r.json(); })
      .then(d => setGenericIndex(d || false))
      .catch(() => setGenericIndex(false));
  }, []);

  /* reset pagination when filters/search change */
  useEffect(() => { setCuratedPage(1); setGenericPage(1); }, [search, specialtyFilter, vendorFilter, tierFilter]);

  /* derived vendor list from curated systems */
  const vendors = useMemo(() => {
    if (!systems) return [];
    return topVendors(systems.items || []);
  }, [systems]);

  /* specialty facet items from taxonomy */
  const specialtyItems = useMemo(() => {
    if (!taxonomy) return [];
    return [
      { value: '__all__', label: 'All systems', count: null },
      ...(taxonomy.specialties || []).map(s => ({ value: s.slug, label: s.display, count: s.count })),
    ];
  }, [taxonomy]);

  /* vendor facet items */
  const vendorItems = useMemo(() => vendors.map(v => ({ value: v.name, label: v.name, count: v.count })), [vendors]);

  /* tier facet items */
  const tierItems = [
    { value: '__all__', label: 'All', count: null },
    { value: 'curated', label: 'Curated', count: null },
    { value: 'generic', label: 'FDA records only', count: null },
  ];

  /* filter curated list */
  const curatedItems = useMemo(() => {
    const raw = systems ? (systems.items || []) : [];
    const q = search.trim().toLowerCase();
    return raw.filter(s => {
      if (specialtyFilter && specialtyFilter !== '__all__' && s.specialty_canonical !== specialtyFilter) return false;
      if (vendorFilter && normalizeVendor(s.vendor) !== vendorFilter) return false;
      if (tierFilter === 'generic') return false;
      if (!matchesQuery(s, q)) return false;
      return true;
    });
  }, [systems, search, specialtyFilter, vendorFilter, tierFilter]);

  /* filter generic list */
  const genericItems = useMemo(() => {
    if (!genericIndex || !genericIndex.items) return [];
    const q = search.trim().toLowerCase();
    return genericIndex.items.filter(s => {
      if (specialtyFilter && specialtyFilter !== '__all__' && s.specialty_canonical !== specialtyFilter) return false;
      if (tierFilter === 'curated') return false;
      if (!matchesQuery(s, q)) return false;
      return true;
    });
  }, [genericIndex, search, specialtyFilter, tierFilter]);

  /* pagination */
  const CURATED_PAGE_SIZE = 60;
  const GENERIC_PAGE_SIZE = 50;
  const shownCurated = curatedItems.slice(0, curatedPage * CURATED_PAGE_SIZE);
  const shownGeneric = genericItems.slice(0, genericPage * GENERIC_PAGE_SIZE);
  const hasMoreCurated = shownCurated.length < curatedItems.length;
  const hasMoreGeneric = shownGeneric.length < genericItems.length;

  /* generic total from index or systems fallback */
  const genericTotal = genericIndex && genericIndex.count != null
    ? genericIndex.count
    : genericIndex && genericIndex.items
      ? genericIndex.items.length
      : 0;

  /* loading state */
  const loading = taxonomy === null || systems === null;

  if (loading && taxErr === null && sysErr === null) {
    return (
      <div className="sxv3 sx sx-browse">
        <div className="bx-page">
          <div className="mono" style={{ padding: '40px 0', color: 'var(--ink-400)', fontSize: 12 }}>loading the dictionary…</div>
        </div>
      </div>
    );
  }

  return (
    <div className="sxv3 sx sx-browse">
      <div className="bx-page">

        {/* topbar */}
        <div className="bx-topbar">
          <span className="bx-logo">
            <span className="bx-logo-mark" />SurgX
          </span>
          <span className="bx-tagline">surgical supply, with receipts</span>
          <input
            className="bx-search"
            type="search"
            value={search}
            onChange={e => setSearch(e.target.value)}
            placeholder="Search systems, vendors, K-numbers…"
            aria-label="Search systems"
          />
        </div>

        {/* per-source fetch errors rendered inline — never a blank page */}
        {taxErr ? (
          <Empty title="taxonomy unavailable" detail={'Could not load specialty taxonomy (' + taxErr + '). Facets are disabled.'} />
        ) : null}
        {sysErr ? (
          <div style={{ marginBottom: 12 }}>
            <Empty title="curated index unavailable" detail={'Could not load curated systems (' + sysErr + ').'} />
          </div>
        ) : null}

        <div className="bx-layout">
          {/* ---- facet rail / mobile chip row ---- */}
          <div className="bx-facet-rail">

            {/* SPECIALTY */}
            {taxonomy && !taxErr ? (
              <FacetPanel
                label="Specialty"
                items={specialtyItems}
                value={specialtyFilter || '__all__'}
                onChange={v => setSpecialtyFilter(v === '__all__' ? null : v)}
              />
            ) : null}

            {/* VENDOR */}
            {vendors.length > 0 && tierFilter !== 'generic' ? (
              <FacetPanel
                label="Vendor"
                items={vendorItems}
                value={vendorFilter}
                onChange={v => setVendorFilter(v)}
              />
            ) : null}

            {/* TIER */}
            <FacetPanel
              label="Tier"
              items={tierItems}
              value={tierFilter || '__all__'}
              onChange={v => setTierFilter(v === '__all__' ? null : v)}
            />

            {/* PROCEDURE — honest non-interactive empty */}
            <div className="bx-facet">
              <div className="bx-facet-head">Procedure</div>
              <div style={{ padding: '8px 12px 10px' }}>
                <Empty
                  title="procedure — growing axis"
                  detail="Only procedures with verified system mappings appear. Unmapped is unmapped — no fake tree."
                />
              </div>
            </div>

          </div>

          {/* ---- main content ---- */}
          <div className="bx-main">

            {/* count bar */}
            <div className="bx-count-bar">
              {!sysErr
                ? shownCurated.length + ' of ' + curatedItems.length + ' curated'
                  + (genericIndex !== null
                    ? ' · ' + (genericIndex ? shownGeneric.length + ' of ' + genericItems.length + ' generic' : '')
                    : ' · loading generic…')
                : null}
            </div>

            {/* curated cards */}
            {!sysErr && curatedItems.length > 0 ? (
              <div className="bx-cards">
                {shownCurated.map(s => (
                  <BrowseCard
                    key={s.slug}
                    slug={s.slug}
                    display_name={s.display_name}
                    vendor={s.vendor}
                    subspecialty={s.subspecialty}
                    fda_510k_primary={s.fda_510k_primary}
                    tier="curated"
                  />
                ))}
              </div>
            ) : !sysErr && systems ? (
              <Empty
                title="no systems match"
                detail={
                  [
                    search ? 'search "' + search + '"' : null,
                    specialtyFilter && specialtyFilter !== '__all__' ? 'specialty: ' + specialtyFilter : null,
                    vendorFilter ? 'vendor: ' + vendorFilter : null,
                    tierFilter ? 'tier: ' + tierFilter : null,
                  ].filter(Boolean).join(' + ') || 'no active filter'
                }
              />
            ) : null}

            {/* show more — curated */}
            {hasMoreCurated ? (
              <div className="bx-show-more">
                <button className="sx-btn quiet sm" onClick={() => setCuratedPage(p => p + 1)}>
                  show 60 more ({curatedItems.length - shownCurated.length} remaining)
                </button>
              </div>
            ) : null}

            {/* generic band */}
            {genericIndex === false ? (
              /* 404 / failed — honest empty, never blank */
              <div style={{ marginTop: 8 }}>
                <Empty title="generic tier index unavailable" detail="The /api/r2/generic-index endpoint is not yet live. It's being built — this band appears when it returns." />
              </div>
            ) : genericIndex && genericIndex.items && tierFilter !== 'curated' ? (
              <div className="bx-generic-band">
                <button
                  className={'bx-generic-toggle sx-card' + (genericOpen ? ' is-open' : '')}
                  onClick={() => setGenericOpen(o => !o)}
                  aria-expanded={genericOpen}
                >
                  <span className="bgt-label">Generic tier · {genericTotal.toLocaleString()} systems — FDA records only</span>
                  <span className="bgt-count">{genericItems.length} matching</span>
                  <span className="bgt-chevron"><Icon d={ICON.chevDown} size={13} /></span>
                </button>

                {genericOpen ? (
                  <>
                    {vendorFilter ? (
                      <div className="bx-vendor-note">vendor facet applies to curated tier — generic band shows all specialty-matched results</div>
                    ) : null}
                    {genericItems.length > 0 ? (
                      <div className="bx-cards">
                        {shownGeneric.map(s => (
                          <BrowseCard
                            key={s.slug}
                            slug={s.slug}
                            display_name={s.display_name}
                            vendor={s.vendor}
                            subspecialty={s.subspecialty}
                            fda_510k_primary={s.fda_510k_primary}
                            tier="generic"
                          />
                        ))}
                      </div>
                    ) : (
                      <Empty title="no generic systems match" detail="Specialty filter applied to generic tier. No matches." />
                    )}
                    {hasMoreGeneric ? (
                      <div className="bx-show-more">
                        <button className="sx-btn quiet sm" onClick={() => setGenericPage(p => p + 1)}>
                          show 50 more ({genericItems.length - shownGeneric.length} remaining)
                        </button>
                      </div>
                    ) : null}
                  </>
                ) : null}
              </div>
            ) : null}

          </div>
        </div>
      </div>
    </div>
  );
}

window.SurgxBrowseV3Root = SurgxBrowseV3Root;
// Mount from inside the transpiled script (same pattern as surgx-pdp-entry.jsx):
// Babel-standalone fetches text/babel src scripts asynchronously, so a separate
// DOMContentLoaded mount races transpilation and blanks the page.
ReactDOM.createRoot(document.getElementById('root')).render(<SurgxBrowseV3Root />);
})();
