// 1Radar v3 — Explore Ads (masonry grid of ad creative cards)

const fmtCompact = (n) => {
  if (n == null) return '—';
  if (n >= 1_000_000) return (n / 1_000_000).toFixed(n % 1_000_000 === 0 ? 0 : 1) + 'M';
  if (n >= 1_000) return (n / 1_000).toFixed(n % 1_000 === 0 ? 0 : 1) + 'K';
  return String(n);
};

// Tiny tinted badge — used for ad-count, winning-count, recency-days top-right cluster.
const Tag = ({ icon, children, tone = 'neutral' }) => {
  const tones = {
    neutral: { bg: 'var(--bg-muted)',         fg: 'var(--text-muted)' },
    green:   { bg: 'rgba(16,185,129,0.12)',   fg: '#047857' },
    amber:   { bg: 'rgba(245,158,11,0.18)',   fg: '#B45309' },
  };
  const t = tones[tone] || tones.neutral;
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center', gap: 3,
      height: 20, padding: '0 6px', borderRadius: 6,
      background: t.bg, color: t.fg,
      fontSize: 11, fontWeight: 600, lineHeight: 1, whiteSpace: 'nowrap',
    }}>
      {icon && <Icon name={icon} size={11}/>}
      {children}
    </span>
  );
};

// Default visibility for every section that can be toggled in "Customize".
// Mirrors Meta's ad anatomy: primary text (caption) + link card (domain,
// headline, description, CTA) on top of the analytics rows.
const AD_CARD_FIELDS_DEFAULT = {
  activity: true, tags: true, rank: true, stats: true, geo: true,
  primaryText: true,
  linkDomain: true, linkHeadline: true, linkDescription: true, linkCta: true,
};
const AD_CARD_FIELD_OPTIONS = [
  { id: 'activity',        label: 'Brand activity',     icon: 'sparkles' },
  { id: 'tags',            label: 'Header tags',        icon: 'tag' },
  { id: 'rank',            label: 'Rank',               icon: 'trend-up' },
  { id: 'stats',           label: 'Performance stats',  icon: 'wallet' },
  { id: 'geo',             label: 'Geo coverage',       icon: 'globe' },
  { id: 'primaryText',     label: 'Primary text',       icon: 'message' },
  { id: 'linkDomain',      label: 'Link domain',        icon: 'link' },
  { id: 'linkHeadline',    label: 'Link headline',      icon: 'tag' },
  { id: 'linkDescription', label: 'Link description',   icon: 'message' },
  { id: 'linkCta',         label: 'CTA button',         icon: 'shop' },
];

// Items in the AdCard "more" menu. Each handler receives the ad.
const AD_CARD_MENU_ITEMS = [
  { id: 'open',     label: 'Open ad details',       icon: 'external' },
  { id: 'copy',     label: 'Copy ad URL',           icon: 'copy' },
  { id: 'folder',   label: 'Add to folder…',        icon: 'folder' },
  { id: 'download', label: 'Download creative',     icon: 'download' },
  { id: 'share',    label: 'Share',                 icon: 'share' },
  { id: 'sep' },
  { id: 'hide',     label: 'Hide brand',            icon: 'eye-off',  danger: false },
  { id: 'report',   label: 'Report ad',             icon: 'flag',     danger: true },
];

const AdCard = ({ ad, onOpen, onSave, onAnalyze, rank, fields }) => {
  const [hover, setHover] = React.useState(false);
  const [savedFolders, setSavedFolders] = React.useState(() => new Set());
  const [menuOpen, setMenuOpen] = React.useState(false);
  const [saveOpen, setSaveOpen] = React.useState(false);
  const [extraFolders, setExtraFolders] = React.useState([]);
  const f = { ...AD_CARD_FIELDS_DEFAULT, ...(fields || {}) };
  const savedToFile = savedFolders.size > 0;

  // Derive display values from existing mock fields.
  const adCount      = 30 + (ad.reach % 600);          // brand active ads
  const totalAds     = adCount + 50 + (ad.reach % 400); // brand total ads
  const winningAds   = (ad.estSpend % 9);              // small winning count
  const dailySpend   = Math.max(1, Math.round(ad.estSpend / Math.max(1, ad.daysActive)));
  const myRank       = rank ?? Math.max(1, ((ad.estSpend % 18) + 1));
  const prevRank     = Math.max(1, myRank + ((ad.reach % 30) - 10));
  const rankWindow   = 30 + (ad.daysActive % 80);
  const geoCoverage  = 60 + (ad.reach % 40);
  const domain       = ad.handle ? ad.handle.replace('@', '').toLowerCase() + '.com' : '';
  const previewUrl   = `https://${domain}/pag...`;

  const isVideo = ad.type === 'Video';

  // Keep hover styling locked while ANY popover is open — otherwise the
  // fixed click-catcher steals mouseleave from the card and makes the lift /
  // shadow flicker as the cursor moves between card and popover.
  const popoverOpen = menuOpen || saveOpen;
  const isHover = hover || popoverOpen;

  return (
    <div
      onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      onClick={() => onOpen && onOpen(ad)}
      style={{
        breakInside: 'avoid', marginBottom: 12,
        background: 'var(--bg-surface)',
        borderRadius: 14,
        border: '1px solid var(--border)',
        overflow: 'hidden',
        cursor: popoverOpen ? 'default' : 'pointer',
        transition: 'box-shadow .15s, transform .15s',
        transform: isHover ? 'translateY(-1px)' : 'none',
        boxShadow: isHover ? '0 10px 28px rgba(14,18,27,0.10)' : '0 1px 2px rgba(14,18,27,0.04)',
      }}>

      {/* Header: brand + activity + right-cluster tags */}
      <div style={{ padding: '10px 12px 8px', display: 'flex', alignItems: 'flex-start', gap: 10 }}>
        <BrandChip name={ad.brand} size={26} square/>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
            {ad.brand}
          </div>
          {f.activity && (
            <div style={{ fontSize: 11, color: 'var(--text-muted)', display: 'flex', alignItems: 'center', gap: 5, marginTop: 1 }}>
              <span style={{ width: 5, height: 5, borderRadius: 999, background: '#10B981' }}/>
              <span style={{ fontFamily: 'var(--font-mono)' }}>{adCount}</span> of <span style={{ fontFamily: 'var(--font-mono)' }}>{totalAds}</span> ads
            </div>
          )}
        </div>
        {f.tags && (
          <div style={{ display: 'inline-flex', alignItems: 'center', gap: 4, flexShrink: 0 }}>
            <Tag icon="copy">{ad.type === 'Carousel' ? Math.max(1, (ad.reach % 5)) : 0}</Tag>
            {winningAds > 0 && <Tag icon="check-circle" tone="green">{winningAds}</Tag>}
            <Tag tone="amber">{ad.daysActive}d</Tag>
          </div>
        )}
      </div>

      {/* Rank row */}
      {f.rank && (
        <div style={{
          padding: '6px 12px',
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          fontSize: 11.5, color: 'var(--text-muted)',
          background: 'var(--bg-muted)',
          borderTop: '1px solid var(--border-soft)',
          borderBottom: '1px solid var(--border-soft)',
        }}>
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, color: 'var(--text)', fontWeight: 600 }}>
            <Icon name="trend-up" size={12} style={{ color: 'var(--text-muted)' }}/>
            Rank <span style={{ fontFamily: 'var(--font-mono)' }}>#{myRank}</span>
          </span>
          <span>
            from <span style={{
              display: 'inline-block', padding: '1px 5px', borderRadius: 4,
              background: 'var(--bg-surface)', border: '1px solid var(--border)',
              fontFamily: 'var(--font-mono)', color: 'var(--text)', fontWeight: 500,
            }}>#{prevRank}</span>
            {' '}in <span style={{
              display: 'inline-block', padding: '1px 5px', borderRadius: 4,
              background: 'var(--bg-surface)', border: '1px solid var(--border)',
              fontFamily: 'var(--font-mono)', color: 'var(--text)', fontWeight: 500,
            }}>{rankWindow}</span> days
          </span>
        </div>
      )}

      {/* Compact stats line + geo */}
      {(f.stats || f.geo) && (
        <div style={{
          padding: '8px 12px',
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          fontSize: 12, color: 'var(--text)', fontFamily: 'var(--font-mono)',
        }}>
          {f.stats ? (
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, color: 'var(--text-muted)' }}>
              <span style={{ color: 'var(--text)' }}>{fmtCompact(ad.reach)}</span>
              <span>·</span>
              <span style={{ color: 'var(--text)' }}>{fmtCompact(ad.estSpend)}€</span>
              <span>·</span>
              <span style={{ color: 'var(--text)' }}>{dailySpend}€/d</span>
            </div>
          ) : <span/>}
          {f.geo && (
            <div style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
              <Icon name={`flag-${ad.country}`} size={12}/>
              <span style={{ color: 'var(--text-muted)', fontSize: 11 }}>{geoCoverage}%</span>
            </div>
          )}
        </div>
      )}

      {/* Creative — most prominent block */}
      <div style={{
        position: 'relative', margin: '0 10px',
        borderRadius: 10, overflow: 'hidden',
        height: Math.max(180, ad.h - 240),
        background: ad.thumb,
      }}>
        <div style={{
          position: 'absolute', inset: 0,
          background: 'linear-gradient(180deg, rgba(0,0,0,0) 65%, rgba(0,0,0,0.32))',
        }}/>
        <div style={{
          position: 'absolute', left: 14, bottom: 14, color: '#fff',
          fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 22, letterSpacing: '-0.02em',
          textTransform: 'uppercase',
        }}>{ad.brand.split(' ')[0]}</div>

        {/* Centered play overlay for videos */}
        {isVideo && (
          <div style={{
            position: 'absolute', inset: 0,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            pointerEvents: 'none',
          }}>
            <div style={{
              width: 48, height: 48, borderRadius: 999,
              background: 'rgba(14,18,27,0.55)',
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              backdropFilter: 'blur(4px)',
            }}>
              <Icon name="play" size={20} style={{ color: '#fff' }}/>
            </div>
          </div>
        )}

        {/* Hover lift (no dark overlay) */}
        {hover && !isVideo && (
          <div style={{
            position: 'absolute', inset: 0,
            background: 'linear-gradient(180deg, rgba(0,0,0,0) 50%, rgba(0,0,0,0.18))',
          }}/>
        )}
      </div>

      {/* Primary text — Meta-style caption above the link card */}
      {f.primaryText && (
        <div style={{
          padding: '12px 14px 6px',
          fontSize: 13, lineHeight: '19px', color: 'var(--text)',
          letterSpacing: '-0.006em',
          overflow: 'hidden', display: '-webkit-box', WebkitLineClamp: 3, WebkitBoxOrient: 'vertical',
        }}>
          {ad.primaryText}
        </div>
      )}

      {/* Link card — domain + headline + description + CTA, all in one box */}
      {(f.linkDomain || f.linkHeadline || f.linkDescription || f.linkCta) && (
        <div style={{ padding: '8px 12px 12px' }}>
          <div style={{
            display: 'flex', alignItems: 'stretch', gap: 8,
            padding: 8, borderRadius: 10,
            background: 'var(--bg-soft)',
            border: '1px solid var(--border-soft)',
          }}>
            <div style={{
              flex: 1, minWidth: 0,
              padding: '2px 4px',
              display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 1,
            }}>
              {f.linkDomain && (
                <div style={{
                  fontSize: 10.5, fontWeight: 500, color: 'var(--text-faint)',
                  letterSpacing: '0.05em', textTransform: 'uppercase',
                  overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
                }}>
                  {domain}
                </div>
              )}
              {f.linkHeadline && (
                <div style={{
                  fontFamily: 'var(--font-display)',
                  fontSize: 13.5, fontWeight: 600, color: 'var(--text)', lineHeight: '18px',
                  letterSpacing: '-0.012em',
                  overflow: 'hidden', display: '-webkit-box', WebkitLineClamp: 1, WebkitBoxOrient: 'vertical',
                }}>
                  {ad.headline}
                </div>
              )}
              {f.linkDescription && (
                <div style={{
                  fontSize: 11.5, color: 'var(--text-muted)', lineHeight: '15px',
                  letterSpacing: '-0.006em',
                  overflow: 'hidden', display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical',
                  marginTop: 1,
                }}>
                  {ad.description}
                </div>
              )}
            </div>
            {f.linkCta && (
              <button onClick={(e) => { e.preventDefault(); e.stopPropagation(); }} style={{
                height: 30, padding: '0 12px', borderRadius: 7,
                border: '1px solid var(--border)',
                background: 'var(--bg-surface)',
                color: 'var(--text)',
                fontFamily: 'var(--font-sans)',
                fontSize: 12, fontWeight: 500,
                letterSpacing: '-0.006em',
                cursor: 'pointer', flexShrink: 0, alignSelf: 'center',
                whiteSpace: 'nowrap',
                boxShadow: 'var(--shadow-card)',
                transition: 'border-color .12s, background .12s',
              }}
              onMouseEnter={(e) => { e.currentTarget.style.borderColor = 'var(--text-faint)'; }}
              onMouseLeave={(e) => { e.currentTarget.style.borderColor = 'var(--border)'; }}>
                {ad.cta || 'Shop Now'}
              </button>
            )}
          </div>
        </div>
      )}

      {/* Footer: split actions — Save | Analyze | kebab menu */}
      <div style={{
        display: 'flex', alignItems: 'stretch',
        borderTop: '1px solid var(--border-soft)',
        background: 'var(--bg-surface)',
      }}>
        <div style={{ flex: 1, position: 'relative' }} onClick={(e) => e.stopPropagation()}>
          <button onClick={() => setSaveOpen(v => !v)} style={{
            width: '100%', height: 38, border: 0,
            background: saveOpen ? 'var(--bg-soft)' : 'transparent',
            display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8,
            fontSize: 12.5, fontWeight: 500,
            color: savedToFile ? 'var(--accent)' : 'var(--text)',
            cursor: 'pointer',
            transition: 'background .12s',
          }}
          onMouseEnter={(e) => { if (!saveOpen) e.currentTarget.style.background = 'var(--bg-soft)'; }}
          onMouseLeave={(e) => { if (!saveOpen) e.currentTarget.style.background = 'transparent'; }}>
            <Icon name="bookmark" size={14}/>
            {savedToFile ? `Saved · ${savedFolders.size}` : 'Save'}
          </button>
          {saveOpen && (
            <SaveToFolderPopup
              folders={[...SAVE_FOLDERS_V3, ...extraFolders]}
              selected={savedFolders}
              onToggle={(id) => {
                setSavedFolders(s => {
                  const n = new Set(s);
                  n.has(id) ? n.delete(id) : n.add(id);
                  return n;
                });
                onSave && onSave(ad);
              }}
              onCreateFolder={(name, choice) => {
                const id = 'custom_' + Date.now();
                setExtraFolders(arr => [...arr, {
                  id, name, count: 0,
                  icon:  choice?.icon  || 'folder',
                  color: choice?.color || 'var(--text-faint)',
                }]);
                setSavedFolders(s => { const n = new Set(s); n.add(id); return n; });
              }}
              onClose={() => setSaveOpen(false)}
              anchor="left"
            />
          )}
        </div>

        <div style={{ width: 1, background: 'var(--border-soft)' }}/>

        <button onClick={(e) => { e.stopPropagation(); onAnalyze && onAnalyze(ad); }} style={{
          flex: 1, height: 38, border: 0, background: 'transparent',
          display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8,
          fontSize: 12.5, fontWeight: 500, color: 'var(--text)',
          cursor: 'pointer',
          transition: 'background .12s',
        }}
        onMouseEnter={(e) => e.currentTarget.style.background = 'var(--bg-muted)'}
        onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
          <Icon name="sparkles" size={14} style={{ color: 'var(--accent)' }}/>
          Analyze
        </button>

        <div style={{ width: 1, background: 'var(--border-soft)' }}/>

        <div style={{ position: 'relative' }} onClick={(e) => e.stopPropagation()}>
          <button onClick={() => setMenuOpen(v => !v)} style={{
            width: 38, height: 38, border: 0,
            background: menuOpen ? 'var(--bg-muted)' : 'transparent',
            color: menuOpen ? 'var(--text)' : 'var(--text-muted)',
            cursor: 'pointer',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            transition: 'background .12s, color .12s',
          }}
          onMouseEnter={(e) => { if (!menuOpen) e.currentTarget.style.background = 'var(--bg-muted)'; }}
          onMouseLeave={(e) => { if (!menuOpen) e.currentTarget.style.background = 'transparent'; }}
          aria-label="More actions">
            <Icon name="more-h" size={16}/>
          </button>
          {menuOpen && (
            <>
              <div onClick={() => setMenuOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 60, cursor: 'default' }}/>
              <div role="menu" style={{
                position: 'absolute', bottom: 'calc(100% + 6px)', right: 0,
                zIndex: 61, width: 220,
                background: 'var(--bg-surface)',
                border: '1px solid var(--border)',
                borderRadius: 10,
                boxShadow: '0 12px 32px rgba(14,18,27,0.12)',
                padding: 4,
                cursor: 'default',
              }}>
                {AD_CARD_MENU_ITEMS.map((item) => {
                  if (item.id === 'sep') return <div key="sep" style={{ height: 1, background: 'var(--border)', margin: '4px 6px' }}/>;
                  const handle = (e) => {
                    e.stopPropagation();
                    setMenuOpen(false);
                    if (item.id === 'open') onOpen && onOpen(ad);
                    if (item.id === 'copy') navigator.clipboard?.writeText(`https://${(ad.handle || '').replace('@','').toLowerCase()}.com`);
                    if (item.id === 'folder') setSaveOpen(true);
                  };
                  return (
                    <button key={item.id} onClick={handle} style={{
                      display: 'flex', alignItems: 'center', gap: 10,
                      width: '100%', padding: '7px 10px',
                      background: 'transparent', border: 0, borderRadius: 6,
                      color: item.danger ? 'var(--danger, #E5484D)' : 'var(--text)',
                      fontSize: 13, textAlign: 'left', cursor: 'pointer',
                    }}
                    onMouseEnter={(e) => e.currentTarget.style.background = 'var(--bg-muted)'}
                    onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
                      <Icon name={item.icon} size={14} style={{ color: item.danger ? 'var(--danger, #E5484D)' : 'var(--text-muted)' }}/>
                      <span style={{ flex: 1 }}>{item.label}</span>
                    </button>
                  );
                })}
              </div>
            </>
          )}
        </div>
      </div>
    </div>
  );
};

// Sort options + comparators for ads. Comparator returns the sort key (numeric
// or string); SortRowV3's direction toggle decides asc/desc.
const AD_SORT_OPTIONS = [
  { id: 'spend',   label: 'Budget spent',  icon: 'wallet',  key: (a) => a.estSpend },
  { id: 'reach',   label: 'Reach',         icon: 'users',   key: (a) => a.reach },
  { id: 'daily',   label: 'Daily spend',   icon: 'wallet',  key: (a) => a.estSpend / Math.max(1, a.daysActive) },
  { id: 'days',    label: 'Days running',  icon: 'calendar',key: (a) => a.daysActive },
  { id: 'recent',  label: 'Most recent',   icon: 'sparkles',key: (a) => -a.daysActive },
  { id: 'ctr',     label: 'CTR',           icon: 'trend-up',key: (a) => parseFloat(a.ctr) },
  { id: 'cpm',     label: 'CPM',           icon: 'trend-up',key: (a) => parseFloat(a.cpm) },
  { id: 'brand',   label: 'Brand (A–Z)',   icon: 'building',key: (a) => a.brand.toLowerCase() },
];

const sortAds = (ads, sortId, direction) => {
  const opt = AD_SORT_OPTIONS.find(o => o.id === sortId) || AD_SORT_OPTIONS[0];
  const sign = direction === 'asc' ? 1 : -1;
  return [...ads].sort((a, b) => {
    const ka = opt.key(a), kb = opt.key(b);
    if (typeof ka === 'string') return sign * ka.localeCompare(kb);
    return sign * (ka - kb);
  });
};

const ExploreV3 = ({ onOpenAd, onAnalyzeAd }) => {
  const [filterMode, setFilterMode] = React.useState('all');
  const [search, setSearch] = React.useState('');
  const [activeChip, setActiveChip] = React.useState([]);
  const [savedIds, setSavedIds] = React.useState({});
  const [sort, setSort] = React.useState('spend');
  const [sortDir, setSortDir] = React.useState('desc');
  const [cardFields, setCardFields] = React.useState(AD_CARD_FIELDS_DEFAULT);

  // Active filters owned here so they can actually filter the grid.
  const mkFilter = (id, icon) => {
    const value = FILTER_DEFS[id]?.initial;
    return { id, icon, value, label: FILTER_DEFS[id]?.label(value) ?? '' };
  };
  const [activeFilters, setActiveFilters] = React.useState(() => [
    mkFilter('date',     'calendar'),
    mkFilter('format',   'play-circle'),
    mkFilter('geo',      'globe'),
    mkFilter('platform', 'sparkles'),
    mkFilter('reach',    'users'),
    mkFilter('spend',    'wallet'),
  ]);

  const chips = [
    { id: 'cta',     label: 'CTA: Shop now',  icon: 'shop' },
    { id: 'audio',   label: 'Audio: Voiceover', icon: 'speaker' },
    { id: 'longvid', label: '> 30s', icon: 'video' },
    { id: 'product', label: 'Has product link', icon: 'link' },
    { id: 'fashion', label: 'Fashion', icon: null },
    { id: 'beauty',  label: 'Beauty',  icon: null },
    { id: 'home',    label: 'Home',    icon: null },
  ];

  const toggleChip = (id) => setActiveChip(a => a.includes(id) ? a.filter(x => x !== id) : [...a, id]);

  const baseAds = ADS_V3.filter(a => {
    if (filterMode !== 'all' && a.type.toLowerCase() !== filterMode) return false;
    if (search && !(a.title.toLowerCase().includes(search.toLowerCase()) || a.brand.toLowerCase().includes(search.toLowerCase()))) return false;
    return true;
  });
  const ads = sortAds(applyFilters(baseAds, activeFilters), sort, sortDir);

  return (
    <>
      <PageHeader
        icon="compass"
        title="Explore Meta Ads"
        subtitle="Browse millions of ads from the Meta Ad Library"
        right={
          <>
            <button className="btn btn-stroke btn-sm">
              <Icon name="play-line" size={14}/> Tutorial
            </button>
            <button className="btn btn-night btn-sm">
              <Icon name="plus" size={14}/> New search
            </button>
          </>
        }
      />

      <FilterStripV3
        filterMode={filterMode} onFilterMode={setFilterMode}
        search={search} onSearch={setSearch}
        chips={chips} activeChip={activeChip} onChip={toggleChip}
        activeFilters={activeFilters}
        onActiveFiltersChange={setActiveFilters}
      />

      <SortRowV3
        count={ads.length * 12348}
        sort={sort}
        sortOptions={AD_SORT_OPTIONS}
        direction={sortDir}
        onSort={setSort}
        onDirection={setSortDir}
        cardFields={cardFields}
        cardFieldOptions={AD_CARD_FIELD_OPTIONS}
        onCardFieldsChange={setCardFields}
      />

      <div style={{ padding: 24, flex: 1, overflow: 'auto' }} className="scroll">
        <div style={{
          columnCount: 4, columnGap: 12,
        }}>
          {ads.map((ad) => (
            <AdCard key={ad.id} ad={ad} fields={cardFields}
                    onOpen={onOpenAd}
                    onAnalyze={onAnalyzeAd}
                    onSave={(a) => setSavedIds(s => ({ ...s, [a.id]: !s[a.id] }))}/>
          ))}
        </div>
      </div>
    </>
  );
};

Object.assign(window, { ExploreV3, AdCard, AD_SORT_OPTIONS, sortAds, AD_CARD_FIELDS_DEFAULT, AD_CARD_FIELD_OPTIONS });
