// Saddle Alert — screens
const { useState: useState_s, useEffect: useEffect_s, useMemo: useMemo_s, useRef: useRef_s } = React;

const PRODUCT_CATEGORIES = [
  { key: 'all', label: 'All' },
  { key: 'bags', label: 'Bags' },
  { key: 'slg', label: 'Small leather goods' },
  { key: 'charms', label: 'Charms' },
];

const productCategoryForModel = (model, title = '') => {
  const definedCategory = window.DW_DATA.MODELS.find(entry => entry.name === model)?.category;
  if (definedCategory) return definedCategory;
  const text = `${model || ''} ${title || ''}`.toLowerCase();
  if (/\b(charm|key ring|key holder|pendant)\b/.test(text)) return 'charms';
  if (/(card holder|wallet|passport holder|change purse|coin purse|\bpouch\b)/.test(text)) return 'slg';
  return 'bags';
};

const categoryLabel = (category) => (
  PRODUCT_CATEGORIES.find(entry => entry.key === category)?.label || 'Bags'
);

const isValidFeedCategory = (value) => (
  PRODUCT_CATEGORIES.some(category => category.key === value)
);

const feedCategoryUserKey = (user) => String(user?.id || user?.email || 'guest');
const hasDisplayPrice = (price) => typeof price === 'number' && Number.isFinite(price) && price > 0;
const priceSymbolForScreen = (currency) => (
  currency === 'USD' ? '$' : currency === 'GBP' ? '£' : '$'
);
const compactCurrencyPrefixForScreen = (currency) => {
  const code = String(currency || '').trim().toUpperCase();
  if (code === 'AUD') return 'A$';
  if (code === 'SGD') return 'S$';
  if (code === 'NZD') return 'NZ$';
  if (code === 'USD') return 'US$';
  if (code === 'GBP') return '£';
  return priceSymbolForScreen(currency);
};
const formatPriceText = (price, currency, fallback = '—') => (
  hasDisplayPrice(price)
    ? `${compactCurrencyPrefixForScreen(currency)}${Math.round(price).toLocaleString()}`
    : fallback
);

const readFeedCategoryPreference = (userKey) => {
  try {
    const prefs = JSON.parse(window.localStorage.getItem('obt:feedCategoryByUser') || '{}');
    const saved = prefs[userKey] || (userKey === 'guest' ? window.localStorage.getItem('obt:feedCategory') : null);
    return isValidFeedCategory(saved) ? saved : 'all';
  } catch {
    return 'all';
  }
};

const writeFeedCategoryPreference = (userKey, category) => {
  try {
    const prefs = JSON.parse(window.localStorage.getItem('obt:feedCategoryByUser') || '{}');
    window.localStorage.setItem('obt:feedCategoryByUser', JSON.stringify({ ...prefs, [userKey]: category }));
  } catch {}
};

const catalogModelId = (name) => String(name || '')
  .normalize('NFD')
  .replace(/[\u0300-\u036f]/g, '')
  .toLowerCase()
  .replace(/[^a-z0-9]+/g, '_')
  .replace(/^_+|_+$/g, '');

const mergedCatalogModels = (captured = []) => {
  const byName = new Map(window.DW_DATA.MODELS.map(entry => [
    entry.name,
    { ...entry, sizes: [...(entry.sizes || [])] },
  ]));
  for (const item of captured) {
    const name = String(item?.model || '').trim();
    if (!name) continue;
    const existing = byName.get(name);
    const capturedSizes = Array.isArray(item.sizes) ? item.sizes : [item.size];
    const observedCount = Number(item.observedCount || item.count || 0);
    const sizes = [...new Set([...(existing?.sizes || []), ...capturedSizes].filter(Boolean))]
      .sort((a, b) => String(a).localeCompare(String(b), undefined, { numeric: true }));
    byName.set(name, {
      id: existing?.id || catalogModelId(name),
      name,
      sizes,
      category: item.category || existing?.category || productCategoryForModel(name, item.title),
      colors: [...new Set([...(existing?.colors || []), ...(Array.isArray(item.colors) ? item.colors : [])].filter(Boolean))],
      materials: [...new Set([...(existing?.materials || []), ...(Array.isArray(item.materials) ? item.materials : [])].filter(Boolean))],
      observedCount: (existing?.observedCount || 0) + observedCount,
      latestSeenAt: item.latestSeenAt || existing?.latestSeenAt || null,
      captured: true,
    });
  }
  return [...byName.values()].sort((a, b) => {
    const observedDiff = Number(Boolean(b.observedCount)) - Number(Boolean(a.observedCount));
    if (observedDiff) return observedDiff;
    return a.name.localeCompare(b.name);
  });
};

const normalizeTypeFiltersUi = (value) => {
  if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
  return Object.fromEntries(Object.entries(value).map(([model, filters]) => [
    model,
    {
      sizes: Array.isArray(filters?.sizes) ? filters.sizes : [],
      colors: Array.isArray(filters?.colors) ? filters.colors : [],
    },
  ]));
};

// ====================================================================
// FEED SCREEN — live drops
// ====================================================================
const FeedScreen = ({ user, alerts, activeRegions, onGoToAdd, onGoToSettings, onGoToAdmin, onGoToMatches, onSelectItem }) => {
  const [regionFilter, setRegionFilter] = useState_s('all');
  const categoryPreferenceKey = feedCategoryUserKey(user);
  const skipNextCategorySave = useRef_s(false);
  const [categoryFilter, setCategoryFilter] = useState_s(() => readFeedCategoryPreference(categoryPreferenceKey));
  const [modelFilter, setModelFilter] = useState_s('all');
  const [searchFilter, setSearchFilter] = useState_s('');
  const [sortBy, setSortBy] = useState_s(() => {
    const key = 'obt:feedSort:guest';
    const allowed = new Set(['newest', 'oldest', 'name_asc', 'price_low', 'price_high', 'color_asc']);
    try {
      const saved = window.localStorage.getItem(key);
      return allowed.has(saved) ? saved : 'newest';
    } catch {
      return 'newest';
    }
  });
  const [sortMenuOpen, setSortMenuOpen] = useState_s(false);
  const [showMatchesOnly, setShowMatchesOnly] = useState_s(false);
  const [liveLoading, setLiveLoading] = useState_s(false);
  const [liveError, setLiveError] = useState_s(null);
  const [liveSnapshot, setLiveSnapshot] = useState_s(null);
  const [checker, setChecker] = useState_s(null);
  const [checkerEvents, setCheckerEvents] = useState_s([]);
  const [channelStatus, setChannelStatus] = useState_s(null);

  const cleanText = (value) => String(value || '')
    .replace(/herm[eè]s/ig, '')
    .replace(/\s+/g, ' ')
    .trim();

  const textKey = (value) => cleanText(value)
    .normalize('NFD')
    .replace(/[\u0300-\u036f]/g, '')
    .toLowerCase();

  const inferModel = (title) => {
    const key = textKey(title);
    const models = [...window.DW_DATA.MODELS].sort((a, b) => b.name.length - a.name.length);
    const match = models.find(m => key.includes(textKey(m.name)));
    return match ? match.name : cleanText(title).replace(/\s+bag\b.*$/i, '').trim() || 'Unknown type';
  };

  const inferSize = (title) => {
    const text = cleanText(title);
    const named = text.match(/\b(mini|micro|elan|slim)\b/i);
    if (named) return named[1].toLowerCase();
    const afterDash = text.match(/-\s*(\d{2})\b/);
    if (afterDash) return afterDash[1];
    const number = text.match(/\b(\d{2})\b/);
    return number ? number[1] : '';
  };

  const inferColor = (title) => {
    const parts = cleanText(title).split(',');
    return parts.length > 1 ? parts[parts.length - 1].trim() : 'Unknown';
  };

  const minutesAgo = (observedAt) => {
    const observed = new Date(observedAt).getTime();
    if (!Number.isFinite(observed)) return 0;
    return Math.max(0, Math.round((Date.now() - observed) / 60000));
  };

  const liveItems = useMemo_s(() => {
    if (!liveSnapshot?.ready) return [];
    return liveSnapshot.items.map((item, idx) => {
      const title = cleanText(item.title);
      const model = item.model || inferModel(title);
      const category = productCategoryForModel(model, title);
      const firstSeenMinutes = minutesAgo(item.firstObservedAt || item.observedAt);
      return {
        id: `${item.region || 'AU'}-LIVE-${idx}-${item.url}`,
        title,
        model,
        category,
        size: item.size || inferSize(title),
        leather: item.material || (title.includes('Toile') || title.includes('Canvas') ? 'Canvas' : 'Leather'),
        color: item.color || inferColor(title),
        hw: 'Hardware',
        region: item.region || 'AU',
        price: item.price,
        currency: item.currency || 'AUD',
        ago: firstSeenMinutes,
        isNew: firstSeenMinutes <= Math.max(35, Number(checker?.intervalMinutes || 30) + 5),
        status: 'live',
        match: false,
        rarity: item.rarity || 'common',
        rarityScore: item.rarityScore || 0,
        rarityBasis: item.rarityBasis || '',
        url: item.url,
        imageUrl: item.imageUrl || null,
        source: `live-${String(item.region || 'AU').toLowerCase()}`,
      };
    });
  }, [liveSnapshot]);

  const itemMatchesAlert = (item, alert) => {
    const parseAlertModels = (value) => {
      if (Array.isArray(value)) return value.map((v) => String(v || '').trim()).filter(Boolean);
      const raw = String(value || '').trim();
      if (!raw) return [];
      if (raw.startsWith('[')) {
        try {
          const parsed = JSON.parse(raw);
          return Array.isArray(parsed) ? parsed.map((v) => String(v || '').trim()).filter(Boolean) : [];
        } catch {
          return [];
        }
      }
      if (raw.includes(',')) return raw.split(',').map((v) => String(v || '').trim()).filter(Boolean);
      return [raw];
    };

    if (!alert?.active) return false;
    const alertCategory = alert.category || 'bags';
    if (alertCategory !== (item.category || productCategoryForModel(item.model, item.title))) return false;
    if (alert.regions?.length && !alert.regions.includes(item.region)) return false;
    const alertModels = Array.isArray(alert.models) && alert.models.length
      ? parseAlertModels(alert.models)
      : alert.model
        ? parseAlertModels(alert.model)
        : [];
    if (!alertModels.length) return false;
    const matchedModel = alertModels.find(model => {
      const wanted = textKey(model);
      return textKey(item.model) === wanted || textKey(item.title).includes(wanted);
    });
    if (!matchedModel) return false;
    const scoped = normalizeTypeFiltersUi(alert.typeFilters)[matchedModel] || {};
    const sizes = scoped.sizes?.length ? scoped.sizes : alert.sizes;
    const colors = scoped.colors?.length ? scoped.colors : alert.colors;
    if (sizes?.length && !sizes.some(s => textKey(s) === textKey(item.size))) return false;
    if (colors?.length && !colors.some(c => textKey(item.color).includes(textKey(c)))) return false;
    if (alert.materials?.length && !alert.materials.some(m => textKey(m) === textKey(item.leather))) return false;
    if (alert.hardware?.length && item.hw !== 'Hardware' && !alert.hardware.includes(item.hw)) return false;
    if (alert.maxPrice && item.price && item.price > alert.maxPrice) return false;
    return true;
  };

  const scopedAlerts = user?.isAdmin
    ? alerts.filter(alert => alert.userId === user.id)
    : alerts;
  const allFeed = liveItems.map(item => ({ ...item, match: scopedAlerts.some(alert => itemMatchesAlert(item, alert)) }));
  const filteredRaw = allFeed.filter(it => {
    if (!activeRegions.includes(it.region)) return false;
    if (regionFilter !== 'all' && it.region !== regionFilter) return false;
    if (categoryFilter !== 'all' && it.category !== categoryFilter) return false;
    if (modelFilter !== 'all' && it.model !== modelFilter) return false;
    if (searchFilter && !textKey(`${it.title} ${it.model} ${it.color}`).includes(textKey(searchFilter))) return false;
    if (showMatchesOnly && !it.match) return false;
    return true;
  });
  const filtered = [...filteredRaw].sort((a, b) => {
    if (sortBy === 'newest') return (a.ago || 0) - (b.ago || 0);
    if (sortBy === 'oldest') return (b.ago || 0) - (a.ago || 0);
    if (sortBy === 'price_low') return (a.price || Number.POSITIVE_INFINITY) - (b.price || Number.POSITIVE_INFINITY);
    if (sortBy === 'price_high') return (b.price || 0) - (a.price || 0);
    if (sortBy === 'color_asc') return String(a.color || '').localeCompare(String(b.color || ''));
    return String(a.title || '').localeCompare(String(b.title || ''));
  });

  const liveCount = filtered.filter(i => i.status === 'live').length;
  const matchCount = filtered.filter(i => i.match).length;
  const regionScopedFeed = allFeed.filter(item => (
    activeRegions.includes(item.region) && (regionFilter === 'all' || item.region === regionFilter)
  ));
  const categoryCounts = PRODUCT_CATEGORIES.reduce((counts, category) => {
    counts[category.key] = category.key === 'all'
      ? regionScopedFeed.length
      : regionScopedFeed.filter(item => item.category === category.key).length;
    return counts;
  }, {});
  const visibleModels = mergedCatalogModels(liveSnapshot?.items || []).filter(model => (
    categoryFilter === 'all' || (model.category || 'bags') === categoryFilter
  ));
  const sortOptions = [
    { value: 'newest', label: 'Newest' },
    { value: 'oldest', label: 'Oldest' },
    { value: 'name_asc', label: 'Name A-Z' },
    { value: 'price_low', label: 'Price low-high' },
    { value: 'price_high', label: 'Price high-low' },
    { value: 'color_asc', label: 'Color A-Z' },
  ];
  const sortLabel = sortOptions.find(option => option.value === sortBy)?.label || 'Newest';

  const loadLiveSnapshot = async (opts = {}) => {
    const silent = Boolean(opts.silent);
    if (!silent) setLiveError(null);
    try {
      const res = await fetch('/api/au-live');
      const json = await res.json();
      setLiveSnapshot(json);
    } catch (err) {
      if (!silent) setLiveError(String(err));
    }
  };

  const loadChecker = async () => {
    try {
      const res = await fetch('/api/checker');
      const json = await res.json();
      setChecker(json.checker || null);
    } catch (err) {}
  };

  const loadCheckerEvents = async () => {
    try {
      const res = await fetch('/api/checker-events?limit=8');
      const json = await res.json();
      if (Array.isArray(json.events)) setCheckerEvents(json.events);
    } catch (err) {}
  };

  const loadNotificationChannels = async () => {
    try {
      const res = await fetch('/api/notification-channels');
      const json = await res.json();
      setChannelStatus(json);
    } catch (err) {}
  };

  const updateChecker = async (patch) => {
    setLiveError(null);
    try {
      const res = await fetch('/api/checker', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(patch),
      });
      const json = await res.json();
      setChecker(json.checker || json.state || null);
      if (json.snapshot) setLiveSnapshot(json.snapshot);
      if (!res.ok) setLiveError(json.stderr || json.error || 'Checker update failed');
      loadCheckerEvents();
    } catch (err) {
      setLiveError(String(err));
    }
  };

  const refreshAuNow = async () => {
    setLiveLoading(true);
    setLiveError(null);
    try {
      await updateChecker({ runNow: true });
    } catch (err) {
      setLiveError(String(err));
    } finally {
      setLiveLoading(false);
    }
  };

  const handleLoadLatest = async () => {
    setLiveLoading(true);
    setLiveError(null);
    try {
      await loadLiveSnapshot();
    } catch (err) {
      setLiveError(String(err));
    } finally {
      setLiveLoading(false);
    }
  };

  useEffect_s(() => {
    const key = `obt:feedSort:${user?.id || 'guest'}`;
    const allowed = new Set(['newest', 'oldest', 'name_asc', 'price_low', 'price_high', 'color_asc']);
    try {
      const saved = window.localStorage.getItem(key);
      if (allowed.has(saved) && saved !== sortBy) setSortBy(saved);
    } catch {}
  }, [user && user.id]);

  useEffect_s(() => {
    const key = `obt:feedSort:${user?.id || 'guest'}`;
    try {
      window.localStorage.setItem(key, sortBy);
    } catch {}
  }, [sortBy, user && user.id]);

  useEffect_s(() => {
    const saved = readFeedCategoryPreference(categoryPreferenceKey);
    if (saved !== categoryFilter) {
      skipNextCategorySave.current = true;
      setCategoryFilter(saved);
      setModelFilter('all');
    }
  }, [categoryPreferenceKey]);

  useEffect_s(() => {
    if (skipNextCategorySave.current) {
      skipNextCategorySave.current = false;
      return;
    }
    writeFeedCategoryPreference(categoryPreferenceKey, categoryFilter);
  }, [categoryFilter, categoryPreferenceKey]);

  useEffect_s(() => {
    loadLiveSnapshot();
    if (user) loadNotificationChannels();
    if (user?.isAdmin) {
      loadChecker();
      loadCheckerEvents();
    }
  }, [user && user.id, user && user.isAdmin]);

  useEffect_s(() => {
    const intervalMinutes = Math.max(1, Number(checker?.intervalMinutes || 30));
    const timer = setInterval(() => {
      loadLiveSnapshot({ silent: true });
    }, intervalMinutes * 60 * 1000);
    return () => clearInterval(timer);
  }, [checker && checker.intervalMinutes]);

  const formatLiveStatus = () => {
    if (!liveSnapshot?.run) return null;
    const staleLabel = liveSnapshot.latestRun && liveSnapshot.latestRun.id !== liveSnapshot.run.id
      ? ` · latest refresh ${liveSnapshot.latestRun.status}`
      : '';
    return `${liveSnapshot.run.status} · ${liveSnapshot.run.itemCount} items${staleLabel}`;
  };

  const isRealProductUrl = (url) => {
    if (!url) return false;
    try {
      const parsed = new URL(url);
      return parsed.protocol.startsWith('http') && parsed.hostname !== 'source.example';
    } catch (err) {
      return false;
    }
  };

  const openProductPage = (url) => {
    if (isRealProductUrl(url)) {
      window.open(url, '_blank', 'noopener,noreferrer');
    }
  };

  const formatDateTime = (value) => {
    if (!value) return '—';
    return new Date(value).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
  };

  const formatBackoff = (value) => {
    if (!value) return null;
    const minutes = Math.max(0, Math.ceil((new Date(value).getTime() - Date.now()) / 60000));
    return minutes > 0 ? `${minutes} min` : null;
  };

  const checkerHealth = checker?.pausedReason
    ? { label: 'Paused', color: 'var(--red)' }
    : checker?.running
      ? { label: 'Checking', color: 'var(--orange)' }
      : checker?.lastOk === false
        ? { label: 'Needs attention', color: 'var(--red)' }
        : checker?.enabled
          ? { label: 'Healthy', color: 'var(--green)' }
          : { label: 'Idle', color: 'var(--ink-3)' };
  const isCloudChecker = checker?.mode === 'cloud';
  const latestCloudEvent = checkerEvents.find(event =>
    event.eventType === 'checker_finished' || event.eventType === 'checker_started' || event.eventType === 'checker_skipped'
  );
  const latestCloudDetails = latestCloudEvent?.details && typeof latestCloudEvent.details === 'object'
    ? latestCloudEvent.details
    : {};
  const snapshotReasonLabel = (reason) => {
    if (reason === 'no_snapshot') return 'No stored storefront snapshot exists yet. Run the cloud checker once after the latest deploy.';
    if (reason === 'no_runs') return 'No checker runs have been recorded yet.';
    if (reason === 'no_successful_snapshot') return 'Checker runs exist, but none have captured storefront items successfully yet.';
    if (reason === 'server_error') return 'The snapshot API hit a server error. Check the latest deployment logs.';
    if (reason === 'sqlite_missing') return 'Local snapshot database has not been created yet.';
    return reason ? `Snapshot status: ${String(reason).replace(/_/g, ' ')}` : 'Waiting for the first real storefront snapshot.';
  };

  const eventStatusStyle = (status) => {
    if (status === 'ok') return { color: 'var(--green)', bg: '#E6F2EC' };
    if (status === 'failed' || status === 'paused') return { color: 'var(--red)', bg: '#FBEAEA' };
    if (status === 'skipped') return { color: 'var(--orange-2)', bg: 'var(--orange-soft)' };
    return { color: 'var(--ink-3)', bg: 'var(--bg)' };
  };
  const telegramConnected = Boolean(channelStatus?.telegram?.connected);
  const hasAnyAlert = alerts.length > 0;
  const hasAnyMatch = matchCount > 0;

  return (
    <div>
      <SectionHead
        title="Live feed"
        subtitle={<span>
          <span className="num" style={{ color: 'var(--ink)', fontWeight: 500 }}>{liveCount}</span> {categoryFilter === 'all' ? 'products' : categoryLabel(categoryFilter).toLowerCase()} in stock now
          <span style={{ margin: '0 8px', color: 'var(--line-2)' }}>·</span>
          <span className="num" style={{ color: 'var(--orange-2)', fontWeight: 500 }}>{matchCount}</span> match your alerts
        </span>}
        action={
          <Btn kind="accent" icon="plus" onClick={onGoToAdd}>New alert</Btn>
        }
      />

      {user && (!hasAnyAlert || !telegramConnected || !hasAnyMatch) && (
        <div style={{
          background: 'var(--bg-elev)',
          border: '1px solid var(--line)',
          borderRadius: 10,
          padding: '14px 16px',
          marginBottom: 14,
        }}>
          <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap', marginBottom: 10 }}>
            <div>
              <div style={{ fontSize: 14, fontWeight: 600 }}>Getting ready</div>
              <div style={{ fontSize: 12.5, color: 'var(--ink-3)', marginTop: 2 }}>
                Once these are green, you can leave the page and wait for alerts.
              </div>
            </div>
            <span className="mono" style={{ color: 'var(--ink-3)', fontSize: 11 }}>
              {[hasAnyAlert, telegramConnected, hasAnyMatch].filter(Boolean).length}/3
            </span>
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(190px, 1fr))', gap: 8 }}>
            {[
              {
                done: hasAnyAlert,
                title: 'Create an alert',
                desc: hasAnyAlert ? `${alerts.length} saved` : 'Pick one or more types to watch',
                action: !hasAnyAlert ? <Btn size="sm" kind="secondary" icon="plus" onClick={onGoToAdd}>New alert</Btn> : null,
              },
              {
                done: telegramConnected,
                title: 'Connect Telegram',
                desc: telegramConnected ? 'Ready for messages' : 'Optional, but best for instant pings',
                action: !telegramConnected ? <Btn size="sm" kind="secondary" onClick={onGoToSettings}>Settings</Btn> : null,
              },
              {
                done: hasAnyMatch,
                title: 'Wait for a match',
                desc: hasAnyMatch ? `${matchCount} live now` : 'Cloud checks keep running',
                action: hasAnyMatch ? <Btn size="sm" kind="secondary" onClick={onGoToMatches}>Review</Btn> : null,
              },
            ].map(step => (
              <div key={step.title} style={{
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'space-between',
                gap: 10,
                padding: '10px 12px',
                border: '1px solid var(--line)',
                borderRadius: 8,
                background: 'var(--bg)',
              }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 9, minWidth: 0 }}>
                  <span style={{
                    width: 18,
                    height: 18,
                    borderRadius: '50%',
                    display: 'inline-flex',
                    alignItems: 'center',
                    justifyContent: 'center',
                    color: step.done ? '#fff' : 'var(--ink-3)',
                    background: step.done ? 'var(--green)' : 'var(--bg-elev)',
                    border: `1px solid ${step.done ? 'var(--green)' : 'var(--line-2)'}`,
                    flexShrink: 0,
                  }}>
                    {step.done ? <Icon name="check" size={12} stroke={2.2} /> : null}
                  </span>
                  <div style={{ minWidth: 0 }}>
                    <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--ink)' }}>{step.title}</div>
                    <div style={{ fontSize: 11.5, color: 'var(--ink-3)' }}>{step.desc}</div>
                  </div>
                </div>
                {step.action}
              </div>
            ))}
          </div>
        </div>
      )}

      <div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 14 }}>
        <Btn size="sm" kind="secondary" icon="refresh" loading={liveLoading} onClick={handleLoadLatest}>
          {liveLoading ? 'Loading...' : 'Load latest feed'}
        </Btn>
      </div>

      {user?.isAdmin && (
        <div style={{
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'space-between',
          gap: 10,
          marginBottom: 14,
          padding: '10px 12px',
          background: 'var(--bg-elev)',
          border: '1px solid var(--line)',
          borderRadius: 10,
          flexWrap: 'wrap',
        }}>
          <div style={{ fontSize: 12, color: 'var(--ink-3)' }}>
            <span style={{ color: 'var(--green)', fontWeight: 600 }}>Cloud checker</span>
            <span style={{ margin: '0 8px', color: 'var(--line-2)' }}>·</span>
            Every {checker?.intervalMinutes || 30} min
            <span style={{ margin: '0 8px', color: 'var(--line-2)' }}>·</span>
            Last run {formatDateTime(latestCloudEvent?.createdAt)}
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <Btn size="sm" kind="secondary" onClick={() => (onGoToAdmin ? onGoToAdmin() : onGoToSettings())}>Admin</Btn>
          </div>
        </div>
      )}

      <div style={{ marginBottom: 12 }}>
        <div style={{ margin: '0 2px 7px' }}>
          <span className="mono" style={{
            fontSize: 10.5,
            color: 'var(--ink-3)',
            letterSpacing: '0.12em',
            textTransform: 'uppercase',
          }}>
            Region
          </span>
        </div>
        <div style={{
          display: 'flex',
          alignItems: 'center',
          gap: 8,
          flexWrap: 'wrap',
          padding: '10px 12px',
          background: 'var(--bg-elev)',
          border: '1px solid var(--line)',
          borderRadius: 10,
        }}>
          <Chip size="sm" accent active={regionFilter === 'all'} onClick={() => setRegionFilter('all')}>All regions</Chip>
          {window.DW_DATA.REGIONS.filter(r => activeRegions.includes(r.code)).map(r => (
            <Chip key={r.code} size="sm" accent active={regionFilter === r.code} title={r.hint || r.name} onClick={() => setRegionFilter(r.code)}>
              <RegionFlags code={r.code} size={12} /> {r.displayCode || r.code}
            </Chip>
          ))}
        </div>
      </div>

      <div style={{ marginBottom: 14 }}>
        <div style={{ margin: '0 2px 7px' }}>
          <span className="mono" style={{
            fontSize: 10.5,
            color: 'var(--ink-3)',
            letterSpacing: '0.12em',
            textTransform: 'uppercase',
          }}>
            Browse
          </span>
        </div>
        <div style={{
          display: 'flex',
          gap: 6,
          padding: 5,
          background: 'var(--bg-elev)',
          border: '1px solid var(--line)',
          borderRadius: 10,
          overflowX: 'auto',
        }}>
          {PRODUCT_CATEGORIES.map(category => {
            const active = categoryFilter === category.key;
            return (
              <button
                key={category.key}
                type="button"
                aria-pressed={active}
                onClick={() => {
                  setCategoryFilter(category.key);
                  setModelFilter('all');
                }}
                style={{
                  display: 'flex',
                  alignItems: 'center',
                  justifyContent: 'center',
                  gap: 8,
                  minWidth: 'max-content',
                  minHeight: 36,
                  padding: '7px 10px',
                  border: `1px solid ${active ? 'var(--orange)' : 'transparent'}`,
                  borderRadius: 7,
                  background: active ? 'var(--orange-soft)' : 'transparent',
                  color: active ? 'var(--orange-2)' : 'var(--ink-2)',
                  fontSize: 13,
                  fontWeight: 600,
                  textAlign: 'left',
                }}
              >
                <span style={{ minWidth: 0, lineHeight: 1.15 }}>{category.label}</span>
                <span className="num" style={{
                  display: 'inline-flex',
                  alignItems: 'center',
                  justifyContent: 'center',
                  minWidth: 24,
                  height: 20,
                  padding: '0 6px',
                  borderRadius: 999,
                  background: active ? 'rgba(232, 83, 24, 0.12)' : 'var(--bg)',
                  color: active ? 'var(--orange-2)' : 'var(--ink-3)',
                  fontSize: 11,
                  fontWeight: 600,
                  flexShrink: 0,
                }}>
                  {categoryCounts[category.key] || 0}
                </span>
              </button>
            );
          })}
        </div>
      </div>

      {/* Filter bar */}
      <div style={{
        display: 'flex',
        alignItems: 'center',
        gap: 10,
        flexWrap: 'wrap',
        marginBottom: 18,
        padding: '12px 14px',
        background: 'var(--bg-elev)',
        border: '1px solid var(--line)',
        borderRadius: 10,
      }}>
        <span style={{ display: 'inline-flex', color: 'var(--ink-3)' }}>
          <Icon name="filter" size={16} />
        </span>
        <select
          value={modelFilter}
          onChange={(e) => setModelFilter(e.target.value)}
          style={{
            border: '1px solid var(--line-2)',
            background: 'var(--bg-elev)',
            borderRadius: 999,
            padding: '5px 28px 5px 12px',
            fontSize: 13,
            fontWeight: 500,
            color: 'var(--ink-2)',
            cursor: 'pointer',
            appearance: 'none',
            backgroundImage: 'url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'12\' height=\'12\' viewBox=\'0 0 24 24\' fill=\'none\' stroke=\'%238A8278\' stroke-width=\'2\' stroke-linecap=\'round\'%3E%3Cpath d=\'m6 9 6 6 6-6\'/%3E%3C/svg%3E")',
            backgroundRepeat: 'no-repeat',
            backgroundPosition: 'right 10px center',
            height: 28,
          }}
        >
          <option value="all">All types</option>
          {visibleModels.map(m => (
            <option key={m.id} value={m.name}>{m.name}</option>
          ))}
        </select>
        <div style={{
          display: 'flex',
          alignItems: 'center',
          gap: 6,
          height: 28,
          minWidth: 148,
          padding: '0 10px',
          border: '1px solid var(--line-2)',
          borderRadius: 999,
          background: 'var(--bg)',
        }}>
          <Icon name="search" size={14} />
          <input
            value={searchFilter}
            onChange={(e) => setSearchFilter(e.target.value)}
            placeholder="Search"
            style={{ width: '100%', minWidth: 0, border: 0, outline: 0, background: 'transparent', fontSize: 13 }}
          />
        </div>
        <div style={{ position: 'relative' }}>
          <button
            type="button"
            onClick={() => setSortMenuOpen(open => !open)}
            style={{
              display: 'inline-flex',
              alignItems: 'center',
              gap: 8,
              border: '1px solid var(--line-2)',
              background: 'var(--bg-elev)',
              borderRadius: 999,
              padding: '5px 12px',
              height: 28,
              fontSize: 13,
              fontWeight: 500,
              color: 'var(--ink-2)',
              cursor: 'pointer',
            }}
          >
            <Icon name="sort" size={14} />
            <span>Sort by</span>
            <span style={{ color: 'var(--ink)' }}>{sortLabel}</span>
            <Icon name="chevron-down" size={13} />
          </button>
          {sortMenuOpen && (
            <div style={{
              position: 'absolute',
              top: 'calc(100% + 6px)',
              left: 0,
              zIndex: 20,
              minWidth: 180,
              border: '1px solid var(--line)',
              background: 'var(--bg-elev)',
              borderRadius: 10,
              boxShadow: '0 8px 18px rgba(22,18,14,0.08)',
              padding: 6,
            }}>
              {sortOptions.map((option) => (
                <button
                  key={option.value}
                  type="button"
                  onClick={() => { setSortBy(option.value); setSortMenuOpen(false); }}
                  style={{
                    width: '100%',
                    textAlign: 'left',
                    border: 0,
                    background: option.value === sortBy ? 'var(--bg)' : 'transparent',
                    borderRadius: 7,
                    padding: '7px 8px',
                    fontSize: 13,
                    color: option.value === sortBy ? 'var(--ink)' : 'var(--ink-2)',
                    cursor: 'pointer',
                  }}
                >
                  {option.label}
                </button>
              ))}
            </div>
          )}
        </div>
        <div style={{ flex: 1 }} />
        <label style={{
          display: 'inline-flex', alignItems: 'center', gap: 8,
          fontSize: 13, color: 'var(--ink-2)', cursor: 'pointer',
        }}>
          <Toggle value={showMatchesOnly} onChange={setShowMatchesOnly} />
          <span>Matches only</span>
        </label>
      </div>

      {/* Feed list */}
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8, animation: 'fadeIn 0.25s ease' }}>
        {filtered.length === 0 && (
          <div style={{
            padding: '60px 24px',
            textAlign: 'center',
            color: 'var(--ink-3)',
            background: 'var(--bg-elev)',
            border: '1px dashed var(--line-2)',
            borderRadius: 12,
          }}>
            <div style={{ fontFamily: 'var(--font-serif)', fontSize: 22, color: 'var(--ink-2)', marginBottom: 4 }}>
              {showMatchesOnly ? 'No live matches right now' : liveSnapshot?.ready ? 'Nothing matches that filter' : 'No real storefront snapshot yet'}
            </div>
            <div style={{ fontSize: 13 }}>
              {showMatchesOnly
                ? hasAnyAlert
                  ? 'The checker is still watching. You can leave this page open or come back later.'
                  : 'Create an alert first, then matching live items will appear here.'
                : liveSnapshot?.ready
                  ? 'Try widening your regions or models.'
                  : snapshotReasonLabel(liveSnapshot?.reason)}
            </div>
          </div>
        )}
        {filtered.map(item => (
          <StockRow key={item.id} item={item} onClick={() => onSelectItem && onSelectItem(item)} />
        ))}
      </div>

      {filtered.length > 0 && (
        <div style={{
          textAlign: 'center',
          padding: '24px 0 8px',
          fontSize: 12,
          color: 'var(--ink-3)',
          fontFamily: 'var(--font-mono)',
          letterSpacing: '0.05em',
        }}>
          — END OF REAL FEED · {liveSnapshot?.run?.finishedAt ? `CHECKED ${formatDateTime(liveSnapshot.run.finishedAt)}` : 'LATEST SNAPSHOT'} —
        </div>
      )}
    </div>
  );
};

// ====================================================================
// ALERTS SCREEN — saved alerts
// ====================================================================
const AlertsScreen = ({ user, alerts, onToggle, onEdit, onDelete, onGoToAdd }) => {
  const [testResult, setTestResult] = useState_s(null);
  const [testingAlertId, setTestingAlertId] = useState_s(null);
  const [expandedUserAlertGroups, setExpandedUserAlertGroups] = useState_s({});
  const [categoryFilter, setCategoryFilter] = useState_s('all');
  const [searchFilter, setSearchFilter] = useState_s('');
  const [activeOnly, setActiveOnly] = useState_s(false);
  const isAdmin = Boolean(user?.isAdmin);
  const ownAlerts = isAdmin ? alerts.filter(alert => alert.userId === user.id) : alerts;
  const otherAlerts = isAdmin ? alerts.filter(alert => alert.userId !== user.id) : [];
  const textKey = (value) => String(value || '').normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
  const alertModels = (alert) => Array.isArray(alert.models) && alert.models.length
    ? alert.models
    : alert.model
      ? [alert.model]
      : [];
  const matchesFilters = (alert) => {
    const category = alert.category || productCategoryForModel(alertModels(alert)[0]);
    if (categoryFilter !== 'all' && category !== categoryFilter) return false;
    if (activeOnly && !alert.active) return false;
    if (searchFilter && !textKey(`${alert.name} ${alertModels(alert).join(' ')}`).includes(textKey(searchFilter))) return false;
    return true;
  };
  const filteredOwnAlerts = ownAlerts.filter(matchesFilters);
  const filteredOtherAlerts = otherAlerts.filter(matchesFilters);
  const otherGroups = filteredOtherAlerts.reduce((groups, alert) => {
    const ownerKey = alert.userId || alert.ownerEmail || 'unknown';
    const ownerLabel = alert.ownerName || alert.ownerEmail || 'Unknown user';
    if (!groups[ownerKey]) groups[ownerKey] = { key: ownerKey, ownerLabel, ownerEmail: alert.ownerEmail, alerts: [] };
    groups[ownerKey].alerts.push(alert);
    return groups;
  }, {});
  const groupedOtherAlerts = Object.values(otherGroups);
  const categoryCounts = PRODUCT_CATEGORIES.reduce((counts, category) => {
    counts[category.key] = category.key === 'all'
      ? ownAlerts.length
      : ownAlerts.filter(alert => (alert.category || productCategoryForModel(alertModels(alert)[0])) === category.key).length;
    return counts;
  }, {});
  const hasFilteredAlerts = filteredOwnAlerts.length > 0 || filteredOtherAlerts.length > 0;

  const openMatch = (url) => {
    if (url) window.open(url, '_blank', 'noopener,noreferrer');
  };

  const testAlert = (id) => {
    setTestingAlertId(id);
    fetch(`/api/alerts/${encodeURIComponent(id)}/test`)
      .then(res => res.json())
      .then(json => {
        if (json.error) throw new Error(json.error);
        setTestResult(json);
      })
      .catch(() => {
        const alert = alerts.find(item => item.id === id);
        setTestResult({
          alert,
          run: null,
          summary: { total: 0, matched: 0, nearMiss: 0 },
          results: [],
          error: 'Could not test this alert',
        });
      })
      .finally(() => setTestingAlertId(null));
  };

  const toggleUserAlertGroup = (key) => {
    setExpandedUserAlertGroups(current => ({
      ...current,
      [key]: !current[key],
    }));
  };

  return (
    <div>
      <SectionHead
        title={isAdmin ? "Alerts" : "My alerts"}
        subtitle={isAdmin
          ? `${ownAlerts.length} yours · ${otherAlerts.length} from users`
          : `${alerts.filter(a => a.active).length} active · ${alerts.length} total`}
        action={<Btn kind="accent" icon="plus" onClick={onGoToAdd}>New alert</Btn>}
      />

      {testResult && (
        <AlertTestPanel
          result={testResult}
          onClose={() => setTestResult(null)}
          onOpen={openMatch}
        />
      )}

      <div style={{
        display: 'grid',
        gap: 10,
        marginBottom: 16,
        padding: '12px 14px',
        background: 'var(--bg-elev)',
        border: '1px solid var(--line)',
        borderRadius: 10,
      }}>
        <div style={{ display: 'flex', gap: 6, overflowX: 'auto' }}>
          {PRODUCT_CATEGORIES.map(category => {
            const active = categoryFilter === category.key;
            return (
              <button
                key={category.key}
                type="button"
                aria-pressed={active}
                onClick={() => setCategoryFilter(category.key)}
                style={{
                  display: 'inline-flex',
                  alignItems: 'center',
                  gap: 7,
                  minWidth: 'max-content',
                  minHeight: 32,
                  padding: '5px 9px',
                  border: `1px solid ${active ? 'var(--orange)' : 'transparent'}`,
                  borderRadius: 7,
                  background: active ? 'var(--orange-soft)' : 'transparent',
                  color: active ? 'var(--orange-2)' : 'var(--ink-2)',
                  fontSize: 12.5,
                  fontWeight: 600,
                  cursor: 'pointer',
                }}
              >
                <span>{category.label}</span>
                <span className="num" style={{ color: active ? 'var(--orange-2)' : 'var(--ink-3)', fontSize: 11 }}>
                  {categoryCounts[category.key] || 0}
                </span>
              </button>
            );
          })}
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
          <div style={{
            display: 'flex',
            alignItems: 'center',
            gap: 7,
            flex: '1 1 220px',
            height: 32,
            padding: '0 10px',
            border: '1px solid var(--line-2)',
            borderRadius: 8,
            background: 'var(--bg)',
          }}>
            <Icon name="search" size={14} />
            <input
              value={searchFilter}
              onChange={(event) => setSearchFilter(event.target.value)}
              placeholder="Search alerts or types"
              style={{ width: '100%', minWidth: 0, border: 0, outline: 0, background: 'transparent', fontSize: 13 }}
            />
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <Toggle value={activeOnly} onChange={setActiveOnly} />
            <span style={{ fontSize: 12.5, color: 'var(--ink-2)' }}>Active only</span>
          </div>
        </div>
      </div>

      <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
        {isAdmin && alerts.length > 0 && (
          <div className="mono" style={{ color: 'var(--ink-3)', fontSize: 11, letterSpacing: '0.08em', textTransform: 'uppercase', marginTop: 2 }}>
            Your alerts
          </div>
        )}
        {filteredOwnAlerts.map(a => (
          <AlertCard
            key={a.id}
            alert={a}
            currentUserId={user?.id}
            showOwner={isAdmin}
            onToggle={onToggle}
            onEdit={onEdit}
            onDelete={onDelete}
            onTest={testAlert}
            testing={testingAlertId === a.id}
          />
        ))}

        {isAdmin && ownAlerts.length === 0 && (
          <div style={{
            padding: '28px 18px',
            background: 'var(--bg-elev)',
            border: '1px dashed var(--line-2)',
            borderRadius: 12,
            color: 'var(--ink-3)',
            fontSize: 13,
          }}>
            You do not have any personal alerts yet.
          </div>
        )}

        {isAdmin && groupedOtherAlerts.length > 0 && (
          <div className="mono" style={{ color: 'var(--ink-3)', fontSize: 11, letterSpacing: '0.08em', textTransform: 'uppercase', marginTop: 10 }}>
            User alerts
          </div>
        )}
        {groupedOtherAlerts.map(group => {
          const expanded = Boolean(expandedUserAlertGroups[group.key]);
          return (
          <div key={group.key} style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            <button
              type="button"
              onClick={() => toggleUserAlertGroup(group.key)}
              style={{
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'space-between',
              gap: 10,
              padding: '10px 14px',
              border: '1px solid var(--line)',
              borderRadius: 10,
              background: 'var(--bg)',
              cursor: 'pointer',
              textAlign: 'left',
            }}>
              <div style={{ minWidth: 0, display: 'flex', alignItems: 'center', gap: 10 }}>
                <span style={{
                  display: 'inline-flex',
                  transform: expanded ? 'rotate(180deg)' : 'none',
                  transition: 'transform 0.12s ease',
                }}>
                  <Icon name="chevron-down" size={14} />
                </span>
                <div style={{ minWidth: 0 }}>
                  <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--ink)' }}>{group.ownerLabel}</div>
                {group.ownerEmail && <div style={{ fontSize: 11.5, color: 'var(--ink-3)' }}>{group.ownerEmail}</div>}
                </div>
              </div>
              <span className="mono" style={{ color: 'var(--ink-3)', fontSize: 11 }}>{group.alerts.length} alerts</span>
            </button>
            {expanded && group.alerts.map(a => (
              <AlertCard
                key={a.id}
                alert={a}
                currentUserId={user?.id}
                showOwner={isAdmin}
                onToggle={onToggle}
                onEdit={onEdit}
                onDelete={onDelete}
                onTest={testAlert}
                testing={testingAlertId === a.id}
              />
            ))}
          </div>
          );
        })}

        {alerts.length === 0 && (
          <div style={{
            padding: '80px 24px',
            textAlign: 'center',
            background: 'var(--bg-elev)',
            border: '1px dashed var(--line-2)',
            borderRadius: 12,
          }}>
            <div style={{ fontFamily: 'var(--font-serif)', fontSize: 26, color: 'var(--ink)' }}>
              No alerts yet
            </div>
            <div style={{ marginTop: 8, color: 'var(--ink-3)' }}>
              Start with a simple type alert. The cloud checker keeps watching after you leave.
            </div>
            <div style={{ marginTop: 18 }}>
              <Btn kind="accent" icon="plus" onClick={onGoToAdd}>Create your first alert</Btn>
            </div>
          </div>
        )}
        {alerts.length > 0 && !hasFilteredAlerts && (
          <div style={{
            padding: '48px 20px',
            textAlign: 'center',
            background: 'var(--bg-elev)',
            border: '1px dashed var(--line-2)',
            borderRadius: 12,
          }}>
            <div style={{ fontFamily: 'var(--font-serif)', fontSize: 23, color: 'var(--ink)' }}>No matching alerts</div>
            <div style={{ marginTop: 7, color: 'var(--ink-3)', fontSize: 13 }}>Try another category, search term, or include inactive alerts.</div>
          </div>
        )}
      </div>
    </div>
  );
};

const AlertTestPanel = ({ result, onClose, onOpen }) => {
  const [showAllFailures, setShowAllFailures] = useState_s(false);
  const fmt = new Intl.NumberFormat('en-US');
  const allRows = result.results || [];
  const visibleRows = showAllFailures
    ? allRows
    : allRows.filter(item => item.matched || item.nearMiss).slice(0, 24);
  const hiddenFailureCount = showAllFailures ? 0 : allRows.filter(item => !item.matched && !item.nearMiss).length;
  return (
    <div style={{
      background: 'var(--bg-elev)',
      border: '1px solid var(--line)',
      borderRadius: 12,
      padding: '16px 18px',
      marginBottom: 14,
    }}>
      <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 12, marginBottom: 12 }}>
        <div>
          <div style={{ fontFamily: 'var(--font-serif)', fontSize: 22, color: 'var(--ink)' }}>
            Test: {result.alert?.name || 'Alert'}
          </div>
          <div style={{ fontSize: 12, color: 'var(--ink-3)', marginTop: 3 }}>
            {result.run
              ? `${result.summary.total} storefront items checked · ${result.summary.matched} match · ${result.summary.nearMiss} near misses · ${result.summary.failed || 0} failed`
              : 'No successful storefront snapshot yet'}
          </div>
        </div>
        <Btn size="sm" kind="ghost" icon="x" onClick={onClose}>Close</Btn>
      </div>

      {result.error && (
        <div style={{ fontSize: 12, color: 'var(--red)', marginBottom: 10 }}>{result.error}</div>
      )}

      {visibleRows.length === 0 && (
        <div style={{
          padding: '28px 14px',
          color: 'var(--ink-3)',
          textAlign: 'center',
          border: '1px dashed var(--line-2)',
          borderRadius: 10,
        }}>
          <div>No current storefront items match or nearly match this alert.</div>
          {hiddenFailureCount > 0 && (
            <div style={{ marginTop: 14 }}>
              <Btn size="sm" kind="secondary" onClick={() => setShowAllFailures(true)}>
                Show all failed items ({hiddenFailureCount})
              </Btn>
            </div>
          )}
        </div>
      )}

      {visibleRows.length > 0 && (
        <div style={{ display: 'flex', flexDirection: 'column' }}>
          {visibleRows.map((item, idx) => (
            <div key={`${item.url}-${idx}`} style={{
              display: 'grid',
              gridTemplateColumns: 'minmax(0, 1fr) auto auto',
              gap: 10,
              alignItems: 'center',
              padding: '10px 0',
              borderTop: idx === 0 ? 'none' : '1px solid var(--line)',
            }}>
              <div style={{ minWidth: 0 }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
                  <span style={{
                    fontFamily: 'var(--font-serif)',
                    fontSize: 17,
                    color: 'var(--ink)',
                    whiteSpace: 'nowrap',
                    overflow: 'hidden',
                    textOverflow: 'ellipsis',
                    maxWidth: '100%',
                  }}>{item.title}</span>
                  <span style={{
                    fontSize: 10.5,
                    fontFamily: 'var(--font-mono)',
                    letterSpacing: '0.05em',
                    textTransform: 'uppercase',
                    padding: '2px 7px',
                    borderRadius: 999,
                    background: item.matched ? '#E6F2EC' : item.nearMiss ? 'var(--orange-soft)' : '#FBEAEA',
                    color: item.matched ? 'var(--green)' : item.nearMiss ? 'var(--orange-2)' : 'var(--red)',
                  }}>{item.matched ? 'Match' : item.nearMiss ? 'Near miss' : 'Failed'}</span>
                </div>
                <div style={{ fontSize: 11.5, color: 'var(--ink-3)', marginTop: 3 }}>
                  {(item.reasons || []).join(' · ') || 'No passing criteria'}
                </div>
                {!item.matched && item.misses?.length > 0 && (
                  <div style={{ fontSize: 11.5, color: 'var(--red)', marginTop: 3 }}>
                    {item.misses.join(' · ')}
                  </div>
                )}
              </div>
              <div style={{ color: 'var(--ink-2)', fontFamily: 'var(--font-mono)', fontSize: 12 }}>
                {formatPriceText(item.price, item.currency)}
              </div>
              <button
                type="button"
                onClick={() => onOpen(item.url)}
                style={{
                  color: 'var(--orange)',
                  fontWeight: 600,
                  textDecoration: 'none',
                  whiteSpace: 'nowrap',
                  border: 0,
                  padding: 0,
                  background: 'transparent',
                  font: 'inherit',
                  cursor: 'pointer',
                }}
              >Open</button>
            </div>
          ))}
          {hiddenFailureCount > 0 && (
            <div style={{
              display: 'flex',
              justifyContent: 'center',
              paddingTop: 12,
              borderTop: '1px solid var(--line)',
            }}>
              <Btn size="sm" kind="secondary" onClick={() => setShowAllFailures(true)}>
                Show all failed items ({hiddenFailureCount})
              </Btn>
            </div>
          )}
          {showAllFailures && (
            <div style={{ textAlign: 'center', paddingTop: 12, borderTop: '1px solid var(--line)' }}>
              <Btn size="sm" kind="ghost" onClick={() => setShowAllFailures(false)}>
                Show matches and near misses only
              </Btn>
            </div>
          )}
        </div>
      )}
    </div>
  );
};

// ====================================================================
// MATCH REVIEW SCREEN — saved match history
// ====================================================================
const MatchReviewScreen = ({ alerts, onSelectItem }) => {
  const [matches, setMatches] = useState_s([]);
  const [loading, setLoading] = useState_s(true);
  const [alertFilter, setAlertFilter] = useState_s('all');
  const [categoryFilter, setCategoryFilter] = useState_s('all');
  const [modelFilter, setModelFilter] = useState_s('all');
  const [sortBy, setSortBy] = useState_s('name_asc');
  const [sortMenuOpen, setSortMenuOpen] = useState_s(false);
  const [colorFilter, setColorFilter] = useState_s('');
  const [dateFilter, setDateFilter] = useState_s('all');
  const [sizeFilter, setSizeFilter] = useState_s('all');
  const [materialFilter, setMaterialFilter] = useState_s('all');
  const [stateFilter, setStateFilter] = useState_s('all');
  const [maxPriceFilter, setMaxPriceFilter] = useState_s('all');
  const [presets, setPresets] = useState_s([]);
  const [presetName, setPresetName] = useState_s('');
  const [liveSnapshot, setLiveSnapshot] = useState_s(null);

  const cleanText = (value) => String(value || '')
    .replace(/herm[eè]s/ig, '')
    .replace(/\s+/g, ' ')
    .trim();

  const textKey = (value) => cleanText(value)
    .normalize('NFD')
    .replace(/[\u0300-\u036f]/g, '')
    .toLowerCase();

  const inferModel = (match) => {
    if (match.model) return match.model;
    const title = textKey(match.title);
    const model = window.DW_DATA.MODELS.find(m => title.includes(textKey(m.name)));
    return model ? model.name : match.alertName || 'Unknown type';
  };

  const inferColor = (match) => {
    if (match.color) return match.color;
    const parts = cleanText(match.title).split(',');
    return parts.length > 1 ? parts[parts.length - 1].trim() : '';
  };

  const inferSize = (match) => {
    if (match.size) return match.size;
    const title = cleanText(match.title);
    const named = title.match(/\b(mini|micro|elan|slim)\b/i);
    if (named) return named[1].toLowerCase();
    const number = title.match(/\b(\d{2})\b/);
    return number ? number[1] : '';
  };

  const minutesAgo = (value) => {
    const timestamp = new Date(value).getTime();
    if (!Number.isFinite(timestamp)) return 0;
    return Math.max(0, Math.floor((Date.now() - timestamp) / 60000));
  };

  const verificationPillStatus = (status) => {
    if (status === 'verified_in_stock') return 'verified';
    if (status === 'verified_unavailable') return 'detailUnavailable';
    if (status === 'verified_unknown') return 'detailUnknown';
    if (status === 'blocked' || status === 'failed') return 'detailFailed';
    return null;
  };

  const canonicalListingKey = (value) => {
    try {
      const parsed = new URL(String(value || ''));
      parsed.search = '';
      parsed.hash = '';
      return parsed.toString().replace(/\/$/, '');
    } catch {
      return String(value || '').trim();
    }
  };

  const loadMatches = () => {
    setLoading(true);
    fetch('/api/matches?limit=100')
      .then(res => res.json())
      .then(json => {
        setMatches(Array.isArray(json.matches) ? json.matches : []);
      })
      .catch(() => setMatches([]))
      .finally(() => setLoading(false));
  };

  const loadPresets = () => {
    fetch('/api/matches/presets')
      .then(res => res.json())
      .then(json => {
        if (Array.isArray(json.presets)) setPresets(json.presets);
      })
      .catch(() => {});
  };

  const loadLiveSnapshot = () => {
    fetch('/api/au-live')
      .then(res => res.json())
      .then(json => setLiveSnapshot(json))
      .catch(() => setLiveSnapshot(null));
  };

  useEffect_s(() => {
    loadMatches();
    loadPresets();
    loadLiveSnapshot();
  }, []);

  const liveUrlSet = useMemo_s(() => {
    const set = new Set();
    if (!liveSnapshot?.ready || !Array.isArray(liveSnapshot.items)) return set;
    for (const item of liveSnapshot.items) {
      set.add(canonicalListingKey(item.url));
    }
    return set;
  }, [liveSnapshot]);

  const currentFilters = () => ({
    alertFilter,
    categoryFilter,
    modelFilter,
    dateFilter,
    sizeFilter,
    materialFilter,
    maxPriceFilter,
    stateFilter,
    colorFilter,
  });

  const applyFilters = (filters) => {
    setAlertFilter(filters.alertFilter || 'all');
    setCategoryFilter(filters.categoryFilter || 'all');
    setModelFilter(filters.modelFilter || 'all');
    setDateFilter(filters.dateFilter || 'all');
    setSizeFilter(filters.sizeFilter || 'all');
    setMaterialFilter(filters.materialFilter || 'all');
    setMaxPriceFilter(filters.maxPriceFilter || 'all');
    setStateFilter(filters.stateFilter || 'all');
    setColorFilter(filters.colorFilter || '');
  };

  const savePreset = () => {
    const name = presetName.trim() || 'Saved view';
    fetch('/api/matches/presets', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ name, filters: currentFilters() }),
    })
      .then(res => res.json())
      .then(json => {
        if (Array.isArray(json.presets)) setPresets(json.presets);
        setPresetName('');
      })
      .catch(() => {});
  };

  const deletePreset = (id) => {
    fetch(`/api/matches/presets/${encodeURIComponent(id)}`, { method: 'DELETE' })
      .then(res => res.json())
      .then(json => {
        if (Array.isArray(json.presets)) setPresets(json.presets);
      })
      .catch(() => {});
  };

  const enriched = useMemo_s(() => matches.map(match => {
    const modelDisplay = inferModel(match);
    return {
    ...match,
    cleanTitle: cleanText(match.detailTitle || match.title),
    modelDisplay,
    category: productCategoryForModel(modelDisplay, match.title),
    sizeDisplay: inferSize(match),
    colorDisplay: inferColor(match),
    materialDisplay: match.material || 'Leather',
    ageMinutes: minutesAgo(match.createdAt),
    firstSeenMinutes: minutesAgo(match.firstSeenAt || match.createdAt),
    latestSeenMinutes: minutesAgo(match.latestSeenAt || match.createdAt),
    detailAgeMinutes: match.detailCheckedAt ? minutesAgo(match.detailCheckedAt) : null,
    verificationStatus: match.detailStatus || 'unverified',
    seenCountDisplay: Number(match.seenCount || 1),
    isInLiveFeed: liveUrlSet.has(canonicalListingKey(match.url)),
  };
  }), [matches, liveUrlSet]);

  const modelOptions = Array.from(new Set(enriched.map(m => m.modelDisplay).filter(Boolean))).sort();
  const sizeOptions = Array.from(new Set(enriched.map(m => m.sizeDisplay).filter(Boolean))).sort();
  const materialOptions = Array.from(new Set(enriched.map(m => m.materialDisplay).filter(Boolean))).sort();
  const sortOptions = [
    { value: 'newest', label: 'Newest' },
    { value: 'oldest', label: 'Oldest' },
    { value: 'name_asc', label: 'Name A-Z' },
    { value: 'price_low', label: 'Price low-high' },
    { value: 'price_high', label: 'Price high-low' },
    { value: 'color_asc', label: 'Color A-Z' },
  ];
  const sortLabel = sortOptions.find(option => option.value === sortBy)?.label || 'Name A-Z';
  const filteredRaw = enriched.filter(match => {
    if (alertFilter !== 'all' && match.alertId !== alertFilter) return false;
    if (categoryFilter !== 'all' && match.category !== categoryFilter) return false;
    if (modelFilter !== 'all' && match.modelDisplay !== modelFilter) return false;
    if (sizeFilter !== 'all' && match.sizeDisplay !== sizeFilter) return false;
    if (materialFilter !== 'all' && match.materialDisplay !== materialFilter) return false;
    if (stateFilter === 'unopened' && match.openedAt) return false;
    if (stateFilter === 'opened' && !match.openedAt) return false;
    if (stateFilter === 'unreviewed' && match.reviewedAt) return false;
    if (stateFilter === 'reviewed' && !match.reviewedAt) return false;
    if (maxPriceFilter !== 'all' && (!match.price || match.price > Number(maxPriceFilter))) return false;
    if (colorFilter && !textKey(match.colorDisplay).includes(textKey(colorFilter))) return false;
    if (dateFilter !== 'all') {
      const created = new Date(match.createdAt).getTime();
      const ageMs = Date.now() - created;
      if (dateFilter === 'today' && ageMs > 24 * 60 * 60 * 1000) return false;
      if (dateFilter === 'week' && ageMs > 7 * 24 * 60 * 60 * 1000) return false;
    }
    return true;
  });
  const filtered = [...filteredRaw].sort((a, b) => {
    if (sortBy === 'newest') return (a.firstSeenMinutes || a.ageMinutes || 0) - (b.firstSeenMinutes || b.ageMinutes || 0);
    if (sortBy === 'oldest') return (b.firstSeenMinutes || b.ageMinutes || 0) - (a.firstSeenMinutes || a.ageMinutes || 0);
    if (sortBy === 'price_low') return (a.price || Number.POSITIVE_INFINITY) - (b.price || Number.POSITIVE_INFINITY);
    if (sortBy === 'price_high') return (b.price || 0) - (a.price || 0);
    if (sortBy === 'color_asc') return String(a.colorDisplay || '').localeCompare(String(b.colorDisplay || ''));
    return String(a.cleanTitle || '').localeCompare(String(b.cleanTitle || ''));
  });
  const groupedMatches = useMemo_s(() => {
    const groups = [];
    const byKey = new Map();
    for (const match of filtered) {
      const basis = match.createdAt || match.firstSeenAt || match.latestSeenAt;
      const day = new Date(basis);
      const key = Number.isFinite(day.getTime()) ? day.toISOString().slice(0, 10) : 'unknown';
      if (!byKey.has(key)) {
        const label = key === 'unknown'
          ? 'Unknown date'
          : day.toLocaleDateString([], { weekday: 'short', day: '2-digit', month: 'short', year: 'numeric' });
        const group = { key, label, items: [] };
        byKey.set(key, group);
        groups.push(group);
      }
      byKey.get(key).items.push(match);
    }
    return groups;
  }, [filtered]);

  const patchMatch = (match, patch) => {
    setMatches(current => current.map(item => {
      if (item.id !== match.id) return item;
      return {
        ...item,
        openedAt: patch.opened === true ? new Date().toISOString() : patch.opened === false ? null : item.openedAt,
        reviewedAt: patch.reviewed === true ? new Date().toISOString() : patch.reviewed === false ? null : item.reviewedAt,
      };
    }));
    fetch(`/api/matches/${encodeURIComponent(match.id)}`, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(patch),
    })
      .then(res => res.json())
      .then(json => {
        if (json.match) {
          setMatches(current => current.map(item => item.id === match.id ? json.match : item));
        }
      })
      .catch(() => {});
  };

  const bulkSetReviewed = (reviewed) => {
    const ids = filtered.map(match => match.id);
    if (!ids.length) return;
    const reviewedAt = reviewed ? new Date().toISOString() : null;
    setMatches(current => current.map(item => (
      ids.includes(item.id) ? { ...item, reviewedAt } : item
    )));
    fetch('/api/matches/bulk-review', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ ids, reviewed }),
    })
      .then(() => loadMatches())
      .catch(() => {});
  };

  const openProductPage = (match) => {
    if (!match.url) return;
    patchMatch(match, { opened: true });
    window.open(match.url, '_blank', 'noopener,noreferrer');
  };

  const openDetail = (match) => {
    patchMatch(match, { opened: true });
    onSelectItem && onSelectItem({
      id: `MATCH-${match.id}`,
      title: match.cleanTitle,
      model: match.modelDisplay,
      category: match.category,
      size: match.sizeDisplay || '',
      leather: match.materialDisplay,
      material: match.materialDisplay,
      color: match.colorDisplay || 'Unknown',
      hw: '',
      region: match.region,
      price: match.price,
      currency: match.currency,
      ago: match.ageMinutes,
      status: match.availability === 'unavailable' ? 'sold' : 'live',
      match: true,
      rarity: match.priority === 'priority' ? 'rare' : 'common',
      url: match.url,
      detailStatus: match.verificationStatus,
    });
  };

  const resetFilters = () => {
    setAlertFilter('all');
    setCategoryFilter('all');
    setModelFilter('all');
    setColorFilter('');
    setDateFilter('all');
    setSizeFilter('all');
    setMaterialFilter('all');
    setStateFilter('all');
    setMaxPriceFilter('all');
  };

  return (
    <div>
      <SectionHead
        title="Matches"
        subtitle={`${filtered.length} shown · ${matches.length} saved`}
        action={
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', justifyContent: 'flex-end' }}>
            <Btn kind="secondary" size="sm" icon="check" onClick={() => bulkSetReviewed(true)} disabled={loading || filtered.length === 0}>
              Review visible
            </Btn>
            <Btn kind="secondary" size="sm" icon="sort" onClick={loadMatches}>{loading ? 'Loading...' : 'Refresh'}</Btn>
          </div>
        }
      />

      <div style={{
        display: 'grid',
        gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))',
        gap: 10,
        marginBottom: 14,
        padding: '12px 14px',
        background: 'var(--bg-elev)',
        border: '1px solid var(--line)',
        borderRadius: 10,
      }}>
        <select value={alertFilter} onChange={(e) => setAlertFilter(e.target.value)} style={matchSelectStyle}>
          <option value="all">All alerts</option>
          {alerts.map(alert => (
            <option key={alert.id} value={alert.id}>{alert.name}</option>
          ))}
        </select>
        <select value={categoryFilter} onChange={(e) => { setCategoryFilter(e.target.value); setModelFilter('all'); }} style={matchSelectStyle}>
          <option value="all">All categories</option>
          {PRODUCT_CATEGORIES.filter(category => category.key !== 'all').map(category => (
            <option key={category.key} value={category.key}>{category.label}</option>
          ))}
        </select>
        <select value={modelFilter} onChange={(e) => setModelFilter(e.target.value)} style={matchSelectStyle}>
          <option value="all">All types</option>
          {modelOptions.filter(model => categoryFilter === 'all' || productCategoryForModel(model) === categoryFilter).map(model => (
            <option key={model} value={model}>{model}</option>
          ))}
        </select>
        <div style={{ position: 'relative' }}>
          <button
            type="button"
            onClick={() => setSortMenuOpen(open => !open)}
            style={{
              ...matchSelectStyle,
              display: 'inline-flex',
              alignItems: 'center',
              gap: 8,
              width: 'auto',
              padding: '0 12px',
              backgroundImage: 'none',
            }}
          >
            <Icon name="sort" size={14} />
            <span style={{ fontFamily: 'var(--font-mono)', fontSize: 12, color: 'var(--ink-3)' }}>Sort by</span>
            <span>{sortLabel}</span>
            <Icon name="chevron-down" size={13} />
          </button>
          {sortMenuOpen && (
            <div style={{
              position: 'absolute',
              top: 'calc(100% + 6px)',
              left: 0,
              zIndex: 20,
              minWidth: 180,
              border: '1px solid var(--line)',
              background: 'var(--bg-elev)',
              borderRadius: 10,
              boxShadow: '0 8px 18px rgba(22,18,14,0.08)',
              padding: 6,
            }}>
              {sortOptions.map((option) => (
                <button
                  key={option.value}
                  type="button"
                  onClick={() => { setSortBy(option.value); setSortMenuOpen(false); }}
                  style={{
                    width: '100%',
                    textAlign: 'left',
                    border: 0,
                    background: option.value === sortBy ? 'var(--bg)' : 'transparent',
                    borderRadius: 7,
                    padding: '7px 8px',
                    fontSize: 13,
                    color: option.value === sortBy ? 'var(--ink)' : 'var(--ink-2)',
                    cursor: 'pointer',
                  }}
                >
                  {option.label}
                </button>
              ))}
            </div>
          )}
        </div>
        <select value={dateFilter} onChange={(e) => setDateFilter(e.target.value)} style={matchSelectStyle}>
          <option value="all">Any date</option>
          <option value="today">Last 24 hours</option>
          <option value="week">Last 7 days</option>
        </select>
        <select value={sizeFilter} onChange={(e) => setSizeFilter(e.target.value)} style={matchSelectStyle}>
          <option value="all">Any size</option>
          {sizeOptions.map(size => (
            <option key={size} value={size}>{size}</option>
          ))}
        </select>
        <select value={materialFilter} onChange={(e) => setMaterialFilter(e.target.value)} style={matchSelectStyle}>
          <option value="all">Any material</option>
          {materialOptions.map(material => (
            <option key={material} value={material}>{material}</option>
          ))}
        </select>
        <select value={maxPriceFilter} onChange={(e) => setMaxPriceFilter(e.target.value)} style={matchSelectStyle}>
          <option value="all">Any price</option>
          <option value="5000">Under $5k</option>
          <option value="10000">Under $10k</option>
          <option value="15000">Under $15k</option>
          <option value="25000">Under $25k</option>
        </select>
        <select value={stateFilter} onChange={(e) => setStateFilter(e.target.value)} style={matchSelectStyle}>
          <option value="all">Any state</option>
          <option value="unopened">Unopened</option>
          <option value="opened">Opened</option>
          <option value="unreviewed">Unreviewed</option>
          <option value="reviewed">Reviewed</option>
        </select>
        <div style={{
          display: 'flex',
          alignItems: 'center',
          gap: 8,
          minWidth: 0,
          height: 36,
          border: '1px solid var(--line-2)',
          borderRadius: 8,
          padding: '0 10px',
          background: 'var(--bg)',
        }}>
          <Icon name="search" size={15} />
          <input
            value={colorFilter}
            onChange={(e) => setColorFilter(e.target.value)}
            placeholder="Color"
            style={{
              border: 0,
              outline: 0,
              background: 'transparent',
              minWidth: 0,
              width: '100%',
              fontSize: 13,
              color: 'var(--ink)',
            }}
          />
        </div>
        <Btn kind="ghost" size="sm" onClick={resetFilters}>Clear</Btn>
      </div>

      <div style={{
        display: 'flex',
        alignItems: 'center',
        gap: 8,
        flexWrap: 'wrap',
        marginBottom: 14,
        padding: '10px 12px',
        background: 'var(--bg-elev)',
        border: '1px solid var(--line)',
        borderRadius: 10,
      }}>
        <div style={{
          display: 'flex',
          alignItems: 'center',
          gap: 8,
          minWidth: 180,
          flex: '1 1 220px',
          height: 34,
          border: '1px solid var(--line-2)',
          borderRadius: 8,
          padding: '0 10px',
          background: 'var(--bg)',
        }}>
          <Icon name="filter" size={15} />
          <input
            value={presetName}
            onChange={(e) => setPresetName(e.target.value)}
            placeholder="Preset name"
            style={{
              border: 0,
              outline: 0,
              background: 'transparent',
              minWidth: 0,
              width: '100%',
              fontSize: 13,
              color: 'var(--ink)',
            }}
          />
        </div>
        <Btn kind="secondary" size="sm" icon="check" onClick={savePreset}>Save view</Btn>
        {presets.map(preset => (
          <span key={preset.id} style={{
            display: 'inline-flex',
            alignItems: 'center',
            gap: 4,
            padding: '3px 5px 3px 9px',
            border: '1px solid var(--line-2)',
            borderRadius: 999,
            background: 'var(--bg)',
          }}>
            <button
              type="button"
              onClick={() => applyFilters(preset.filters || {})}
              style={{
                border: 0,
                padding: 0,
                background: 'transparent',
                color: 'var(--ink-2)',
                cursor: 'pointer',
                fontSize: 12,
                fontWeight: 500,
              }}
            >{preset.name}</button>
            <button
              type="button"
              onClick={() => deletePreset(preset.id)}
              aria-label={`Delete ${preset.name}`}
              style={{
                display: 'inline-flex',
                alignItems: 'center',
                justifyContent: 'center',
                width: 18,
                height: 18,
                border: 0,
                borderRadius: 999,
                background: 'transparent',
                color: 'var(--ink-3)',
                cursor: 'pointer',
              }}
            >
              <Icon name="x" size={12} />
            </button>
          </span>
        ))}
      </div>

      {loading && (
        <div style={{
          padding: '36px 24px',
          color: 'var(--ink-3)',
          textAlign: 'center',
          background: 'var(--bg-elev)',
          border: '1px solid var(--line)',
          borderRadius: 12,
        }}>Loading saved matches...</div>
      )}

      {!loading && filtered.length === 0 && (
        <div style={{
          padding: '64px 24px',
          textAlign: 'center',
          color: 'var(--ink-3)',
          background: 'var(--bg-elev)',
          border: '1px dashed var(--line-2)',
          borderRadius: 12,
        }}>
          <div style={{ fontFamily: 'var(--font-serif)', fontSize: 24, color: 'var(--ink)', marginBottom: 6 }}>
            {matches.length === 0 ? 'No matches yet' : 'No matches in this view'}
          </div>
          <div style={{ fontSize: 13 }}>
            {matches.length === 0
              ? alerts.length > 0
                ? 'Checks are running in the background. New matches will appear here and in your inbox.'
                : 'Create an alert first, then matching items will collect here.'
              : 'Clear filters or widen the view.'}
          </div>
        </div>
      )}

      {!loading && filtered.length > 0 && (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
          {groupedMatches.map(group => (
            <div key={group.key} style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
              <div className="mono" style={{ color: 'var(--ink-3)', fontSize: 11, letterSpacing: '0.08em', textTransform: 'uppercase', padding: '6px 2px 2px' }}>
                {group.label} · {group.items.length}
              </div>
              {group.items.map(match => (
            <div key={match.id} style={{
              display: 'grid',
              gridTemplateColumns: '44px minmax(0, 1fr) auto',
              gap: 12,
              alignItems: 'center',
              padding: '13px 14px',
              background: 'var(--bg-elev)',
              border: '1px solid var(--line)',
              borderRadius: 10,
              borderLeft: match.priority === 'priority' ? '3px solid var(--orange)' : '1px solid var(--line)',
            }}>
              <div style={{
                width: 44,
                height: 44,
                borderRadius: 7,
                background: findColorSwatch(match.colorDisplay)?.swatch || '#ccc',
                boxShadow: 'inset 0 0 0 1px rgba(0,0,0,0.12)',
              }} />
              <button
                type="button"
                onClick={() => openDetail(match)}
                style={{
                  minWidth: 0,
                  textAlign: 'left',
                  border: 0,
                  padding: 0,
                  background: 'transparent',
                  cursor: 'pointer',
                }}
              >
                <div style={{
                  display: 'flex',
                  alignItems: 'center',
                  gap: 8,
                  flexWrap: 'wrap',
                  marginBottom: 4,
                }}>
                  <span style={{
                    fontFamily: 'var(--font-serif)',
                    fontSize: 18,
                    color: 'var(--ink)',
                    whiteSpace: 'nowrap',
                    overflow: 'hidden',
                    textOverflow: 'ellipsis',
                    maxWidth: '100%',
                  }}>{match.cleanTitle}</span>
                  <Chip size="sm">{categoryLabel(match.category)}</Chip>
                  {match.priority === 'priority' && <StatusPill status="priority" />}
                  {verificationPillStatus(match.verificationStatus) && (
                    <StatusPill status={verificationPillStatus(match.verificationStatus)} />
                  )}
                </div>
                <div style={{
                  display: 'flex',
                  gap: 7,
                  flexWrap: 'wrap',
                  fontSize: 12,
                  color: 'var(--ink-3)',
                }}>
                  <span>{match.alertName}</span>
                  <span style={{ color: 'var(--line-2)' }}>·</span>
                  <span>{match.modelDisplay}</span>
                  {match.sizeDisplay && (
                    <>
                      <span style={{ color: 'var(--line-2)' }}>·</span>
                      <span>{match.sizeDisplay}</span>
                    </>
                  )}
                  {match.colorDisplay && (
                    <>
                      <span style={{ color: 'var(--line-2)' }}>·</span>
                      <span>{match.colorDisplay}</span>
                    </>
                  )}
                  <span style={{ color: 'var(--line-2)' }}>·</span>
                  <span>{timeAgo(match.ageMinutes)}</span>
                  {match.openedAt && (
                    <>
                      <span style={{ color: 'var(--line-2)' }}>·</span>
                      <span>opened</span>
                    </>
                  )}
                  {match.reviewedAt && (
                    <>
                      <span style={{ color: 'var(--line-2)' }}>·</span>
                      <span>reviewed</span>
                    </>
                  )}
                  {match.detailAgeMinutes !== null && (
                    <>
                      <span style={{ color: 'var(--line-2)' }}>·</span>
                      <span>detail checked {timeAgo(match.detailAgeMinutes)}</span>
                    </>
                  )}
                </div>
                <div style={{
                  marginTop: 4,
                  display: 'flex',
                  gap: 7,
                  flexWrap: 'wrap',
                  fontSize: 11.5,
                  color: 'var(--ink-3)',
                }}>
                  <span>first {timeAgo(match.firstSeenMinutes)}</span>
                  <span style={{ color: 'var(--line-2)' }}>·</span>
                  <span>latest {timeAgo(match.latestSeenMinutes)}</span>
                  <span style={{ color: 'var(--line-2)' }}>·</span>
                  <span>seen {match.seenCountDisplay}x</span>
                </div>
                {match.matchReason && (
                  <div style={{
                    marginTop: 4,
                    fontSize: 11.5,
                    color: 'var(--ink-3)',
                    whiteSpace: 'nowrap',
                    overflow: 'hidden',
                    textOverflow: 'ellipsis',
                  }}>{match.matchReason}</div>
                )}
              </button>
              <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 7 }}>
                <div style={{ fontFamily: 'var(--font-mono)', fontSize: 12, color: 'var(--ink-2)' }}>
                  {match.price ? `${match.currency || ''} ${Math.round(match.price).toLocaleString()}` : '—'}
                </div>
                <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                  <RegionBadge code={match.region} />
                  {match.isInLiveFeed && match.availability !== 'unavailable' && match.detailAvailability !== 'unavailable' && match.verificationStatus !== 'verified_unavailable' ? (
                    <button
                      type="button"
                      onClick={() => openProductPage(match)}
                      style={{
                        display: 'inline-flex',
                        alignItems: 'center',
                        gap: 4,
                        color: 'var(--orange)',
                        fontWeight: 600,
                        border: 0,
                        padding: 0,
                        background: 'transparent',
                        cursor: 'pointer',
                        fontSize: 12,
                      }}
                    >
                      Open <Icon name="external" size={13} />
                    </button>
                  ) : (
                    <span style={{ fontSize: 12, color: 'var(--ink-3)', fontWeight: 500 }}>
                      Now out of stock
                    </span>
                  )}
                  <button
                    type="button"
                    onClick={() => patchMatch(match, { reviewed: !match.reviewedAt })}
                    style={{
                      display: 'inline-flex',
                      alignItems: 'center',
                      gap: 4,
                      color: match.reviewedAt ? 'var(--green)' : 'var(--ink-3)',
                      fontWeight: 600,
                      border: 0,
                      padding: 0,
                      background: 'transparent',
                      cursor: 'pointer',
                      fontSize: 12,
                    }}
                  >
                    {match.reviewedAt ? 'Reviewed' : 'Review'}
                  </button>
                </div>
              </div>
            </div>
              ))}
            </div>
          ))}
        </div>
      )}
    </div>
  );
};

const matchSelectStyle = {
  height: 36,
  border: '1px solid var(--line-2)',
  borderRadius: 8,
  background: 'var(--bg)',
  color: 'var(--ink)',
  fontSize: 13,
  padding: '0 10px',
  minWidth: 0,
};

const AlertCard = ({ alert, currentUserId, showOwner, onToggle, onEdit, onDelete, onTest, testing }) => {
  const [actionsOpen, setActionsOpen] = useState_s(false);
  const notifyChannels = Array.isArray(alert.notifyChannels) && alert.notifyChannels.length ? alert.notifyChannels : ['in_app'];
  const alertModels = Array.isArray(alert.models) && alert.models.length
    ? alert.models
    : alert.model
      ? [alert.model]
      : [];
  const channelLabels = {
    in_app: 'In-app',
    telegram: 'Telegram',
  };
  const belongsToCurrentUser = alert.userId === currentUserId;
  const ownerLabel = belongsToCurrentUser
    ? 'You'
    : alert.ownerName || alert.ownerEmail || 'Unknown user';
  return (
    <div style={{
      padding: '16px 18px',
      background: 'var(--bg-elev)',
      border: '1px solid var(--line)',
      borderRadius: 12,
      opacity: alert.active ? 1 : 0.65,
      transition: 'opacity 0.15s ease',
    }}>
      <div style={{
        display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12,
        marginBottom: 12,
      }}>
        <div style={{ minWidth: 0, flex: 1 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
            <span style={{ fontFamily: 'var(--font-serif)', fontSize: 20, color: 'var(--ink)' }}>
              {alert.name}
            </span>
            {alert.priority === 'priority' && (
              <span style={{
                display: 'inline-flex', alignItems: 'center', gap: 4,
                fontFamily: 'var(--font-mono)',
                fontSize: 10, fontWeight: 600, letterSpacing: '0.06em',
                padding: '3px 7px',
                background: 'var(--orange)',
                color: '#fff',
                borderRadius: 3,
              }}>★ PRIORITY</span>
            )}
            {alert.hits > 0 && (
              <span style={{
                fontSize: 11.5,
                fontWeight: 500,
                color: 'var(--orange-2)',
                background: 'var(--orange-soft)',
                padding: '3px 9px',
                borderRadius: 999,
              }}>
                {alert.hits} hit{alert.hits > 1 ? 's' : ''} · {alert.lastHit}
              </span>
            )}
            {showOwner && (
              <span style={{
                fontSize: 11.5,
                fontWeight: 600,
                color: belongsToCurrentUser ? 'var(--green)' : 'var(--ink-3)',
                background: belongsToCurrentUser ? '#E6F2EC' : 'var(--bg)',
                border: '1px solid var(--line)',
                padding: '3px 8px',
                borderRadius: 999,
              }}>
                {ownerLabel}
              </span>
            )}
          </div>
          {showOwner && !belongsToCurrentUser && alert.ownerEmail && (
            <div style={{ color: 'var(--ink-3)', fontSize: 11.5, marginTop: 3 }}>
              {alert.ownerEmail}
            </div>
          )}
        </div>
        <Toggle value={alert.active} onChange={() => onToggle(alert.id)} />
      </div>

      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 14 }}>
        <Chip size="sm">{categoryLabel(alert.category || productCategoryForModel(alertModels[0]))}</Chip>
        {alertModels.slice(0, 3).map(model => <Chip key={model} size="sm">{model}</Chip>)}
        {alertModels.length > 3 && <Chip size="sm">+{alertModels.length - 3} types</Chip>}
        {alertModels.map(model => {
          const sizes = alert.typeFilters?.[model]?.sizes || [];
          return sizes.length ? <Chip key={`${model}-sizes`} size="sm">{model}: {sizes.join(', ')}</Chip> : null;
        })}
        {!Object.keys(alert.typeFilters || {}).length && alert.sizes.length > 0 && <Chip size="sm">Size {alert.sizes.join(', ')}</Chip>}
        {alert.colors.length > 0 ? alert.colors.slice(0, 4).map(c => {
          const sw = window.DW_DATA.COLORS.find(x => x.name === c);
          return <Chip key={c} size="sm" color={sw && sw.swatch}>{c}</Chip>;
        }) : <Chip size="sm">Any color</Chip>}
        {alert.colors.length > 4 && <Chip size="sm">+{alert.colors.length - 4}</Chip>}
        {alert.materials?.length > 0 && <Chip size="sm">{alert.materials.join(' / ')}</Chip>}
        {alert.hardware.length > 0 && <Chip size="sm">{alert.hardware.join(' / ')} hw</Chip>}
        {notifyChannels.map(channel => (
          <Chip
            key={channel}
            size="sm"
            style={{
              background: channel === 'telegram' ? '#E6F2EC' : 'var(--bg)',
              color: channel === 'telegram' ? 'var(--green)' : 'var(--ink-3)',
            }}
          >
            {channelLabels[channel] || channel}
          </Chip>
        ))}
      </div>

      <div style={{
        display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap',
        paddingTop: 12, borderTop: '1px solid var(--line)',
      }}>
        <div style={{ display: 'flex', gap: 4 }}>
          {alert.regions.map(r => {
            const reg = window.DW_DATA.REGIONS.find(x => x.code === r);
            return <RegionFlags key={r} code={r} size={14} />;
          })}
        </div>
        <span style={{ fontSize: 12.5, color: 'var(--ink-3)' }}>
          {typeof alert.maxPrice === 'number' && alert.maxPrice > 0
            ? <>under <span className="num" style={{ color: 'var(--ink-2)', fontWeight: 500 }}>${alert.maxPrice.toLocaleString()}</span></>
            : <>any price</>}
        </span>
        <span style={{ fontSize: 12.5, color: 'var(--ink-3)' }}>· created {alert.created}</span>
        <div style={{ flex: 1 }} />
        <div style={{ display: 'flex', gap: 6, position: 'relative' }}>
          <Btn size="sm" kind="ghost" icon="edit" onClick={() => onEdit(alert.id)}>Edit</Btn>
          <button
            type="button"
            aria-label={`More actions for ${alert.name}`}
            aria-expanded={actionsOpen}
            onClick={() => setActionsOpen(open => !open)}
            style={{
              display: 'inline-flex',
              alignItems: 'center',
              justifyContent: 'center',
              width: 30,
              height: 30,
              border: '1px solid var(--line-2)',
              borderRadius: 8,
              background: 'var(--bg-elev)',
              color: 'var(--ink-2)',
              cursor: 'pointer',
            }}
          >
            <Icon name="more-horizontal" size={16} />
          </button>
          {actionsOpen && (
            <div style={{
              position: 'absolute',
              right: 0,
              bottom: 36,
              zIndex: 5,
              minWidth: 138,
              padding: 4,
              background: 'var(--bg-elev)',
              border: '1px solid var(--line)',
              borderRadius: 8,
              boxShadow: '0 8px 24px rgba(30, 24, 18, 0.12)',
            }}>
              <button
                type="button"
                disabled={testing}
                onClick={() => { setActionsOpen(false); onTest(alert.id); }}
                style={{
                  display: 'flex', alignItems: 'center', gap: 7, width: '100%',
                  padding: '7px 8px', border: 0, borderRadius: 5, background: 'transparent',
                  color: 'var(--ink-2)', cursor: testing ? 'not-allowed' : 'pointer', fontSize: 12.5, textAlign: 'left',
                }}
              >
                <Icon name="search" size={14} /> {testing ? 'Testing...' : 'Test alert'}
              </button>
              <button
                type="button"
                onClick={() => { setActionsOpen(false); onDelete(alert.id); }}
                style={{
                  display: 'flex', alignItems: 'center', gap: 7, width: '100%',
                  padding: '7px 8px', border: 0, borderRadius: 5, background: 'transparent',
                  color: 'var(--red)', cursor: 'pointer', fontSize: 12.5, textAlign: 'left',
                }}
              >
                <Icon name="trash" size={14} /> Delete
              </button>
            </div>
          )}
        </div>
      </div>
    </div>
  );
};

// ====================================================================
// ADD ALERT SCREEN — guided two-step form
// ====================================================================
const AddAlertScreen = ({ onCancel, onSave, editingAlert, prefill }) => {
  const CATEGORY_OPTIONS = [
    { key: 'bags', label: 'Bags' },
    { key: 'slg', label: 'Small leather goods' },
    { key: 'charms', label: 'Charms' },
  ];
  const seed = editingAlert || prefill || {};
  const parseModelList = (value) => {
    if (Array.isArray(value)) return value.map(v => String(v || '').trim()).filter(Boolean);
    if (typeof value === 'string' && value.trim().startsWith('[')) {
      try {
        const parsed = JSON.parse(value);
        return Array.isArray(parsed) ? parsed.map(v => String(v || '').trim()).filter(Boolean) : [];
      } catch {
        return [];
      }
    }
    return value ? [String(value).trim()].filter(Boolean) : [];
  };
  const isFreshAlert = !editingAlert && !prefill;
  const seedModels = parseModelList(seed.models || seed.model || prefill?.model || '');
  const seedHasSpecificFilters =
    (seed.sizes && seed.sizes.length > 0) ||
    (seed.typeFilters && Object.keys(seed.typeFilters).length > 0) ||
    (seed.colors && seed.colors.length > 0) ||
    (seed.materials && seed.materials.length > 0) ||
    (seed.hardware && seed.hardware.length > 0);
  const seedMaxPriceRaw = Number(seed.maxPrice);
  const seedMaxPrice = Number.isFinite(seedMaxPriceRaw) && seedMaxPriceRaw > 0 && !(seedMaxPriceRaw === 25000 && !seedHasSpecificFilters)
    ? seedMaxPriceRaw
    : null;
  const enabledRegionCodes = ['AU', 'SG'];
  const defaultRegions = ['AU'];
  const seedIsAdvanced =
    seedHasSpecificFilters ||
    (seed.regions && seed.regions.some(r => !defaultRegions.includes(r))) ||
    (seedMaxPrice !== null);
  const [alertMode, setAlertMode] = useState_s(seedIsAdvanced ? 'advanced' : 'simple');
  const [name, setName] = useState_s(seed.name || (prefill ? `${prefill.model} ${prefill.size || ''}`.trim() : ''));
  const [models, setModels] = useState_s(seedModels.length ? seedModels : []);
  const inferredSeedCategory = seed.category || (seedModels[0] ? productCategoryForModel(seedModels[0]) : '');
  const [category, setCategory] = useState_s(isFreshAlert ? '' : inferredSeedCategory);
  const initialTypeFilters = (() => {
    const existing = normalizeTypeFiltersUi(seed.typeFilters);
    if (Object.keys(existing).length) return existing;
    const legacySizes = seed.sizes || (prefill?.size ? [prefill.size] : []);
    return Object.fromEntries(seedModels.map(model => [model, { sizes: legacySizes, colors: [] }]));
  })();
  const [typeFilters, setTypeFilters] = useState_s(initialTypeFilters);
  const [customizeColorsByType, setCustomizeColorsByType] = useState_s(
    Object.values(initialTypeFilters).some(filters => filters.colors?.length)
  );
  const [colors, setColors] = useState_s(seed.colors || (prefill?.color ? [prefill.color] : []));
  const [materials, setMaterials] = useState_s(seed.materials || (prefill?.leather ? [prefill.leather] : []));
  const [hardware, setHardware] = useState_s(seed.hardware || (prefill?.hw ? [prefill.hw] : []));
  const [regions, setRegions] = useState_s((seed.regions && seed.regions.length ? seed.regions.filter(r => enabledRegionCodes.includes(r)) : defaultRegions));
  const [maxPrice, setMaxPrice] = useState_s(seedMaxPrice || (prefill?.price ? Math.round(prefill.price * 1.1 / 500) * 500 : 25000));
  const [priceCapped, setPriceCapped] = useState_s(seedMaxPrice !== null);
  const [priority, setPriority] = useState_s(seed.priority || 'standard');
  const [notifyChannels, setNotifyChannels] = useState_s(seed.notifyChannels?.length ? seed.notifyChannels : ['in_app']);
  const [channelStatus, setChannelStatus] = useState_s(null);
  const [typeSearch, setTypeSearch] = useState_s('');
  const [livePreviewItems, setLivePreviewItems] = useState_s([]);
  const [capturedTypes, setCapturedTypes] = useState_s([]);
  const [livePreviewLoading, setLivePreviewLoading] = useState_s(false);
  const [livePreviewError, setLivePreviewError] = useState_s(null);
  const [step, setStep] = useState_s('target');

  const textKeyLocal = (value) => String(value || '').normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().trim();
  const catalogModels = useMemo_s(() => mergedCatalogModels(capturedTypes), [capturedTypes]);
  const categoryModels = category
    ? catalogModels.filter((entry) => (entry.category || 'bags') === category)
    : [];
  const visibleCategoryModels = useMemo_s(() => {
    const query = textKeyLocal(typeSearch);
    if (!query) return categoryModels;
    return categoryModels.filter((entry) => textKeyLocal(entry.name).includes(query));
  }, [categoryModels, typeSearch]);
  const selectedCatalogModels = categoryModels.filter((entry) => models.includes(entry.name));
  const unselectedVisibleModels = visibleCategoryModels.filter((entry) => !models.includes(entry.name));
  const capturedVisibleModels = unselectedVisibleModels.filter((entry) => entry.captured || Number(entry.observedCount || 0) > 0);
  const fallbackVisibleModels = unselectedVisibleModels.filter((entry) => !(entry.captured || Number(entry.observedCount || 0) > 0));
  const visibleModelNames = new Set(categoryModels.map((entry) => entry.name));
  const effectiveModels = models.filter((model) => visibleModelNames.has(model));
  const liveItemsForModel = livePreviewItems.filter(item => effectiveModels.some(model => {
    const wanted = textKeyLocal(model);
    return textKeyLocal(item.model) === wanted || textKeyLocal(item.title).includes(wanted);
  }));
  const catalogColors = effectiveModels.flatMap(model => catalogModels.find(entry => entry.name === model)?.colors || []);
  const availableColors = [...new Set([...catalogColors, ...liveItemsForModel.map(item => item.color)].filter(Boolean))];
  const catalogMaterials = effectiveModels.flatMap(model => catalogModels.find(entry => entry.name === model)?.materials || []);
  const availableMaterials = [...new Set(['Canvas', 'Leather', ...catalogMaterials, ...liveItemsForModel.map(item => item.material).filter(Boolean)])];
  const selectedSizes = effectiveModels.flatMap(model => typeFilters[model]?.sizes || []);
  const selectedMarketLabel = regions
    .map(code => window.DW_DATA.REGIONS.find(entry => entry.code === code)?.displayCode || code)
    .join(' + ') || 'No market';

  const toggleIn = (arr, val) => arr.includes(val) ? arr.filter(x => x !== val) : [...arr, val];
  const availableSizesForModel = (model) => {
    const defined = catalogModels.find(entry => entry.name === model)?.sizes || [];
    const live = livePreviewItems
      .filter(item => textKeyLocal(item.model) === textKeyLocal(model) || textKeyLocal(item.title).includes(textKeyLocal(model)))
      .map(item => item.size)
      .filter(Boolean);
    return [...new Set([...defined, ...live])];
  };
  const availableColorsForModel = (model) => {
    const catalogColors = catalogModels.find(entry => entry.name === model)?.colors || [];
    const live = livePreviewItems
      .filter(item => textKeyLocal(item.model) === textKeyLocal(model) || textKeyLocal(item.title).includes(textKeyLocal(model)))
      .map(item => item.color)
      .filter(Boolean);
    const captured = [...new Set([...catalogColors, ...live])];
    return captured.length ? captured : window.DW_DATA.COLORS.map(entry => entry.name);
  };
  const updateTypeFilter = (model, key, value) => {
    setTypeFilters(current => ({
      ...current,
      [model]: {
        sizes: current[model]?.sizes || [],
        colors: current[model]?.colors || [],
        [key]: value,
      },
    }));
  };
  const toggleNotifyChannel = (channel) => {
    setNotifyChannels(current => {
      const next = current.includes(channel) ? current.filter(x => x !== channel) : [...current, channel];
      return next.length ? next : current;
    });
  };
  const toggleModel = (entry) => {
    setModels(current => toggleIn(current, entry.name));
    setTypeFilters(current => current[entry.name]
      ? current
      : { ...current, [entry.name]: { sizes: [], colors: [] } });
  };
  const TypeChoice = ({ entry }) => {
    const active = models.includes(entry.name);
    const observedCount = Number(entry.observedCount || 0);
    const sizes = Array.isArray(entry.sizes) ? entry.sizes.filter(Boolean) : [];
    const colors = Array.isArray(entry.colors) ? entry.colors.filter(Boolean) : [];
    return (
      <button
        type="button"
        onClick={() => toggleModel(entry)}
        style={{
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'space-between',
          gap: 12,
          width: '100%',
          padding: '11px 12px',
          background: active ? 'var(--orange-soft)' : 'var(--bg)',
          border: `1px solid ${active ? 'var(--orange)' : 'var(--line)'}`,
          borderRadius: 9,
          cursor: 'pointer',
          textAlign: 'left',
        }}
      >
        <span style={{ minWidth: 0 }}>
          <span style={{ display: 'block', color: active ? 'var(--orange-2)' : 'var(--ink)', fontSize: 13, fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
            {entry.name}
          </span>
          <span style={{ display: 'block', color: 'var(--ink-3)', fontSize: 11.5, marginTop: 2, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
            {observedCount ? `${observedCount.toLocaleString()} observations` : 'Curated fallback'}
            {sizes.length ? ` · ${sizes.slice(0, 4).join(', ')}` : ''}
            {colors.length ? ` · ${colors.slice(0, 3).join(', ')}` : ''}
          </span>
        </span>
        <span style={{
          width: 18,
          height: 18,
          borderRadius: '50%',
          display: 'inline-flex',
          alignItems: 'center',
          justifyContent: 'center',
          flexShrink: 0,
          border: `1px solid ${active ? 'var(--orange)' : 'var(--line-2)'}`,
          background: active ? 'var(--orange)' : 'var(--bg-elev)',
          color: '#fff',
        }}>
          {active ? <Icon name="check" size={12} stroke={2.2} /> : null}
        </span>
      </button>
    );
  };

  const hasAlertName = Boolean(name.trim());
  const valid = hasAlertName && notifyChannels.length > 0 && regions.length > 0 && Boolean(category) && effectiveModels.length > 0;
  const targetReady = hasAlertName && Boolean(category) && effectiveModels.length > 0;
  const missingTargetFields = [
    !hasAlertName && 'alert name',
    !category && 'category',
    effectiveModels.length === 0 && 'at least one type',
  ].filter(Boolean);

  const loadLivePreview = () => {
    setLivePreviewLoading(true);
    setLivePreviewError(null);
    return fetch('/api/au-live')
      .then(res => {
        if (!res.ok) throw new Error(`Snapshot check failed (${res.status})`);
        return res.json();
      })
      .then(json => {
        if (Array.isArray(json.items)) {
          setLivePreviewItems(json.items);
        } else {
          setLivePreviewItems([]);
        }
      })
      .catch(err => {
        setLivePreviewError(err.message || 'Could not test against the current snapshot.');
      })
      .finally(() => setLivePreviewLoading(false));
  };

  useEffect_s(() => {
    loadLivePreview();
    fetch('/api/catalog-types')
      .then(res => res.json())
      .then(json => setCapturedTypes(Array.isArray(json.types) ? json.types : []))
      .catch(() => {});
  }, []);

  useEffect_s(() => {
    fetch('/api/notification-channels')
      .then(res => res.json())
      .then(json => setChannelStatus(json))
      .catch(() => {});
  }, []);

  const previewMatches = useMemo_s(() => {
    return livePreviewItems.filter(item => {
      if (regions.length && !regions.includes(item.region || 'AU')) return false;
      if (category && productCategoryForModel(item.model, item.title) !== category) return false;
      const matchedModel = effectiveModels.find(model => {
        const wanted = textKeyLocal(model);
        return textKeyLocal(item.model) === wanted || textKeyLocal(item.title).includes(wanted);
      });
      if (!matchedModel) return false;
      const scoped = typeFilters[matchedModel] || {};
      if (scoped.sizes?.length && !scoped.sizes.some(size => textKeyLocal(size) === textKeyLocal(item.size))) return false;
      const scopedColors = customizeColorsByType ? scoped.colors : colors;
      if (scopedColors?.length && !scopedColors.some(color => textKeyLocal(item.color).includes(textKeyLocal(color)))) return false;
      if (materials.length && !materials.some(material => textKeyLocal(material) === textKeyLocal(item.material))) return false;
      if (priceCapped && maxPrice && item.price && item.price > maxPrice) return false;
      return true;
    });
  }, [livePreviewItems, effectiveModels, typeFilters, customizeColorsByType, colors, materials, regions, maxPrice, priceCapped]);

  const summary = useMemo_s(() => {
    const modelLabel = effectiveModels.map(model => {
      const sizes = typeFilters[model]?.sizes || [];
      return sizes.length ? `${model} ${sizes.join('/')}` : model;
    }).join(', ');
    const parts = [modelLabel || (category ? categoryLabel(category) : 'choose category + types')];
    if (colors.length) parts.push(colors.length <= 2 ? colors.join(', ') : `${colors.length} colors`);
    else parts.push('any color');
    if (materials.length) parts.push(materials.join('/'));
    return parts.join(' · ');
  }, [effectiveModels, typeFilters, colors, materials, category]);

  const alertQualityHints = useMemo_s(() => {
    const hints = [];
    const isBroad = alertMode === 'simple' || (!selectedSizes.length && !colors.length && !materials.length && !priceCapped);
    const hasTelegram = notifyChannels.includes('telegram');
    const telegramReady = channelStatus?.telegram?.enabled && channelStatus?.telegram?.connected;

    if (previewMatches.length > 0) {
      hints.push({ tone: 'good', text: `${previewMatches.length} current storefront item${previewMatches.length === 1 ? '' : 's'} would trigger this alert now.` });
    } else if (livePreviewItems.length > 0) {
      hints.push({ tone: 'neutral', text: 'No current storefront items match yet. That is fine for a future-drop alert.' });
    } else if (livePreviewError) {
      hints.push({ tone: 'warn', text: 'The live snapshot test could not run. The alert can still be saved.' });
    } else {
      hints.push({ tone: 'neutral', text: 'Current snapshot test will appear once storefront inventory is available.' });
    }

    if (isBroad) {
      hints.push({ tone: 'warn', text: 'Broad alert: it watches any size, color, and material for this type.' });
    } else {
      hints.push({ tone: 'good', text: 'Filters are specific enough to avoid most unrelated matches.' });
    }

    if (livePreviewItems.length > 0 && liveItemsForModel.length > 0 && previewMatches.length === 0 && (selectedSizes.length || colors.length || materials.length || priceCapped)) {
      hints.push({ tone: 'warn', text: 'This type exists in the snapshot, but your filters are excluding the current items.' });
    }

    if (hasTelegram && !telegramReady) {
      hints.push({ tone: 'warn', text: 'Telegram is selected but not connected yet. Connect it in Settings before relying on it.' });
    } else if (hasTelegram) {
      hints.push({ tone: 'good', text: 'Telegram is connected for instant messages.' });
    } else {
      hints.push({ tone: 'neutral', text: 'This alert will notify through the in-app inbox.' });
    }

    return hints;
  }, [alertMode, selectedSizes, colors, materials, maxPrice, priceCapped, notifyChannels, channelStatus, previewMatches, livePreviewItems, liveItemsForModel, livePreviewError]);

  const saveAlert = () => {
    const alertName = name.trim();
    const savedTypeFilters = Object.fromEntries(effectiveModels.map(model => [
      model,
      {
        sizes: alertMode === 'advanced' ? (typeFilters[model]?.sizes || []) : [],
        colors: alertMode === 'advanced' && customizeColorsByType ? (typeFilters[model]?.colors || []) : [],
      },
    ]));
    const payload = alertMode === 'simple'
      ? {
          name: alertName,
          category,
          model: effectiveModels[0] || '',
          models: effectiveModels,
          typeFilters: savedTypeFilters,
          sizes: [],
          colors: [],
          hardware: [],
          materials: [],
          regions,
          maxPrice: null,
          priority,
          notifyChannels,
        }
      : {
          name: alertName,
          category,
          model: effectiveModels[0] || '',
          models: effectiveModels,
          typeFilters: savedTypeFilters,
          sizes: [],
          colors: customizeColorsByType ? [] : colors,
          hardware,
          materials,
          regions,
          maxPrice: priceCapped ? maxPrice : null,
          priority,
          notifyChannels,
        };
    onSave(payload);
  };

  return (
    <div>
      <SectionHead
        title={editingAlert ? 'Edit alert' : 'New alert'}
        subtitle="Tell us what you're hunting for. We'll ping you when it drops."
      />

      <div style={{
        background: 'var(--bg-elev)',
        border: '1px solid var(--line)',
        borderRadius: 14,
        padding: 'clamp(20px, 4vw, 32px)',
      }}>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>
          <Field label="Alert name" required hint="Use a name you will recognise later">
            <TextInput value={name} onChange={setName} placeholder="e.g. Red Herbag watch" />
          </Field>

          <div style={{
            display: 'grid',
            gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
            gap: 8,
            paddingBottom: 18,
            borderBottom: '1px solid var(--line)',
          }}>
            {[
              { id: 'target', number: '01', label: 'What to watch', hint: 'Category, types, and filters' },
              { id: 'delivery', number: '02', label: 'Delivery', hint: 'Priority, notifications, and review' },
            ].map(item => {
              const active = step === item.id;
              const complete = item.id === 'target' && step === 'delivery';
              return (
                <button
                  key={item.id}
                  type="button"
                  onClick={() => item.id === 'target' || targetReady ? setStep(item.id) : null}
                  style={{
                    display: 'flex',
                    alignItems: 'flex-start',
                    gap: 10,
                    minWidth: 0,
                    padding: '11px 12px',
                    background: active ? 'var(--orange-soft)' : complete ? '#E6F2EC' : 'var(--bg)',
                    border: `1px solid ${active ? 'var(--orange)' : 'var(--line)'}`,
                    borderRadius: 8,
                    cursor: item.id === 'delivery' && !targetReady ? 'not-allowed' : 'pointer',
                    textAlign: 'left',
                  }}
                >
                  <span className="mono" style={{ color: active ? 'var(--orange-2)' : complete ? 'var(--green)' : 'var(--ink-3)', fontSize: 11 }}>
                    {complete ? 'OK' : item.number}
                  </span>
                  <span style={{ minWidth: 0 }}>
                    <span style={{ display: 'block', color: 'var(--ink)', fontSize: 13, fontWeight: 600 }}>{item.label}</span>
                    <span style={{ display: 'block', color: 'var(--ink-3)', fontSize: 11.5, marginTop: 2 }}>{item.hint}</span>
                  </span>
                </button>
              );
            })}
          </div>

          {step === 'target' && (
            <>
          <Field label="Alert mode">
            <div style={{ display: 'flex', gap: 8 }}>
              {[
                { id: 'simple', label: 'Simple', hint: 'Types only' },
                { id: 'advanced', label: 'Advanced', hint: 'Drill down to types, sizes, colors, materials, and price' },
              ].map(opt => (
                <button
                  key={opt.id}
                  type="button"
                  onClick={() => setAlertMode(opt.id)}
                  style={{
                    flex: 1,
                    display: 'flex',
                    flexDirection: 'column',
                    alignItems: 'flex-start',
                    gap: 2,
                    padding: '10px 12px',
                    background: alertMode === opt.id ? 'var(--orange-soft)' : 'transparent',
                    color: alertMode === opt.id ? 'var(--orange-2)' : 'var(--ink)',
                    border: `1px solid ${alertMode === opt.id ? 'var(--orange)' : 'var(--line)'}`,
                    borderRadius: 8,
                    cursor: 'pointer',
                    textAlign: 'left',
                  }}
                >
                  <span style={{ fontSize: 13, fontWeight: 600 }}>{opt.label}</span>
                  <span style={{ fontSize: 11, color: 'var(--ink-3)' }}>{opt.hint}</span>
                </button>
              ))}
            </div>
          </Field>

          <Field label="Category" hint="Each alert can only track one category">
            <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
              {CATEGORY_OPTIONS.map(option => (
                <Chip
                  key={option.key}
                  accent
                  active={option.key === category}
                  onClick={() => {
                    setCategory(option.key);
                    setModels(current => current.filter(model =>
                      (catalogModels.find(entry => entry.name === model)?.category || 'bags') === option.key
                    ));
                    setTypeFilters(current => Object.fromEntries(
                      Object.entries(current).filter(([model]) =>
                        (catalogModels.find(entry => entry.name === model)?.category || 'bags') === option.key
                      )
                    ));
                  }}
                >{option.label}</Chip>
              ))}
            </div>
            <div style={{ marginTop: 8, fontSize: 12, color: 'var(--ink-3)' }}>
              Each alert is scoped to one category. Create separate alerts for Bags, Small leather goods, and Charms.
            </div>
          </Field>

          {/* Type */}
          <Field label="Types" hint={category ? `${effectiveModels.length} selected` : 'Choose category first'}>
            <div style={{ marginBottom: 8 }}>
              <TextInput
                value={typeSearch}
                onChange={setTypeSearch}
                placeholder={category ? 'Search item or type' : 'Select category first to search types'}
                icon="search"
              />
            </div>
            {!category && (
              <div style={{ fontSize: 12, color: 'var(--ink-3)', marginBottom: 8 }}>
                Pick a category first, then choose one or more types in that category.
              </div>
            )}
            {category && selectedCatalogModels.length > 0 && (
              <div style={{ marginBottom: 10 }}>
                <div className="mono" style={{ color: 'var(--ink-3)', fontSize: 10.5, textTransform: 'uppercase', marginBottom: 6 }}>
                  Selected
                </div>
                <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 8 }}>
                  {selectedCatalogModels.map(entry => <TypeChoice key={`selected-${entry.id}`} entry={entry} />)}
                </div>
              </div>
            )}
            {category && capturedVisibleModels.length > 0 && (
              <div style={{ marginBottom: fallbackVisibleModels.length ? 10 : 0 }}>
                <div className="mono" style={{ color: 'var(--ink-3)', fontSize: 10.5, textTransform: 'uppercase', marginBottom: 6 }}>
                  Captured from storefront
                </div>
                <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 8 }}>
                  {capturedVisibleModels.map(entry => <TypeChoice key={entry.id} entry={entry} />)}
                </div>
              </div>
            )}
            {category && fallbackVisibleModels.length > 0 && (
              <div>
                <div className="mono" style={{ color: 'var(--ink-3)', fontSize: 10.5, textTransform: 'uppercase', marginBottom: 6 }}>
                  Suggested types
                </div>
                <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 8 }}>
                  {fallbackVisibleModels.map(entry => <TypeChoice key={entry.id} entry={entry} />)}
                </div>
              </div>
            )}
            {category && visibleCategoryModels.length === 0 && (
              <div style={{ fontSize: 12, color: 'var(--ink-3)', marginTop: 8 }}>
                No types matched that search. Try another keyword.
              </div>
            )}
          </Field>

          {alertMode === 'advanced' && (
            <>
              {/* Sizes by selected type */}
              <Field label="Sizes by type" hint="Each type keeps its own sizes">
                <div style={{ display: 'grid', gap: 10 }}>
                  {effectiveModels.map(model => {
                    const choices = availableSizesForModel(model);
                    const selected = typeFilters[model]?.sizes || [];
                    return (
                      <div key={model} style={{ padding: '12px 14px', background: 'var(--bg)', border: '1px solid var(--line)', borderRadius: 10 }}>
                        <div style={{ display: 'flex', justifyContent: 'space-between', gap: 10, marginBottom: choices.length ? 8 : 0 }}>
                          <span style={{ fontSize: 13, fontWeight: 600 }}>{model}</span>
                          <span style={{ fontSize: 12, color: 'var(--ink-3)' }}>{selected.length ? `${selected.length} selected` : 'Any size'}</span>
                        </div>
                        {choices.length ? (
                          <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
                            {choices.map(size => (
                              <Chip key={size} accent active={selected.includes(size)} onClick={() => updateTypeFilter(model, 'sizes', toggleIn(selected, size))}>
                                {size}
                              </Chip>
                            ))}
                          </div>
                        ) : (
                          <div style={{ fontSize: 12, color: 'var(--ink-3)' }}>No size choices for this type.</div>
                        )}
                      </div>
                    );
                  })}
                </div>
              </Field>

              {/* Colors */}
              <Field label="Colors" hint={customizeColorsByType ? 'Customized by type' : `${colors.length} selected across all types`}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
                  <Toggle value={customizeColorsByType} onChange={setCustomizeColorsByType} />
                  <span style={{ fontSize: 12.5, color: 'var(--ink-2)' }}>Choose different colors for each type</span>
                </div>
                {customizeColorsByType ? (
                  <div style={{ display: 'grid', gap: 10 }}>
                    {effectiveModels.map(model => {
                      const selected = typeFilters[model]?.colors || [];
                      return (
                        <div key={model} style={{ padding: '12px 14px', background: 'var(--bg)', border: '1px solid var(--line)', borderRadius: 10 }}>
                          <div style={{ display: 'flex', justifyContent: 'space-between', gap: 10, marginBottom: 8 }}>
                            <span style={{ fontSize: 13, fontWeight: 600 }}>{model}</span>
                            <span style={{ fontSize: 12, color: 'var(--ink-3)' }}>{selected.length ? `${selected.length} selected` : 'Any color'}</span>
                          </div>
                          <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
                            {availableColorsForModel(model).map(name => {
                              const swatch = window.DW_DATA.COLORS.find(c => c.name === name)?.swatch;
                              return <Chip key={name} accent color={swatch} active={selected.includes(name)} onClick={() => updateTypeFilter(model, 'colors', toggleIn(selected, name))}>{name}</Chip>;
                            })}
                          </div>
                        </div>
                      );
                    })}
                  </div>
                ) : (
                  <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
                    {(availableColors.length
                      ? availableColors.map(name => window.DW_DATA.COLORS.find(c => c.name === name) || { name, swatch: undefined })
                      : window.DW_DATA.COLORS
                    ).map(c => (
                      <Chip key={c.name} accent color={c.swatch} active={colors.includes(c.name)} onClick={() => setColors(toggleIn(colors, c.name))}>{c.name}</Chip>
                    ))}
                  </div>
                )}
              </Field>

              {/* Material */}
              <Field label="Material" hint="Leave empty for any">
                <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
                  {availableMaterials.map(h => (
                    <Chip key={h} accent active={materials.includes(h)} onClick={() => setMaterials(toggleIn(materials, h))}>
                      {h}
                    </Chip>
                  ))}
                </div>
              </Field>
            </>
          )}

          <div style={{
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'space-between',
            gap: 12,
            flexWrap: 'wrap',
            padding: '12px 14px',
            background: 'var(--bg)',
            border: '1px solid var(--line)',
            borderRadius: 10,
          }}>
            <div>
              <div style={{ fontSize: 13, fontWeight: 600 }}>Market</div>
              <div style={{ marginTop: 2, color: 'var(--ink-3)', fontSize: 12 }}>Each alert watches selected storefronts only. AU also serves New Zealand, with eligibility confirmed at checkout.</div>
            </div>
            <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', justifyContent: 'flex-end' }}>
              {window.DW_DATA.REGIONS.filter(r => enabledRegionCodes.includes(r.code)).map(r => (
                <Chip
                  key={r.code}
                  accent
                  active={regions.includes(r.code)}
                  title={r.hint || r.name}
                  onClick={() => setRegions(current => {
                    const next = current.includes(r.code)
                      ? current.filter(code => code !== r.code)
                      : [...current, r.code];
                    return next.length ? next : current;
                  })}
                >
                  <RegionFlags code={r.code} size={13} /> {r.displayCode || r.code}
                </Chip>
              ))}
            </div>
          </div>

          {alertMode === 'advanced' && (
            <Field label="Max price" hint={priceCapped ? `Under $${maxPrice.toLocaleString()}` : 'Any price'}>
              <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginBottom: priceCapped ? 10 : 0 }}>
                <Chip accent active={!priceCapped} onClick={() => setPriceCapped(false)}>Any price</Chip>
                <Chip accent active={priceCapped} onClick={() => setPriceCapped(true)}>Set cap</Chip>
              </div>
              {priceCapped && (
                <>
                  <input
                    type="range"
                    min="1000" max="25000" step="500"
                    value={maxPrice}
                    onChange={e => setMaxPrice(parseInt(e.target.value))}
                    style={{ width: '100%', accentColor: 'var(--orange)' }}
                  />
                  <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11, color: 'var(--ink-3)', fontFamily: 'var(--font-mono)', marginTop: 4 }}>
                    <span>$1,000</span><span>$25,000+</span>
                  </div>
                </>
              )}
            </Field>
          )}
            </>
          )}

          {step === 'delivery' && (
            <>
          <div style={{
            padding: '14px 16px',
            background: 'var(--orange-soft)',
            border: '1px solid var(--line)',
            borderRadius: 10,
          }}>
            <div className="mono" style={{ color: 'var(--orange-2)', fontSize: 11, textTransform: 'uppercase' }}>Watching {selectedMarketLabel}</div>
            <div style={{ color: 'var(--ink)', fontFamily: 'var(--font-serif)', fontSize: 19, marginTop: 5 }}>{summary}</div>
            <button
              type="button"
              onClick={() => setStep('target')}
              style={{ marginTop: 9, padding: 0, border: 0, background: 'transparent', color: 'var(--orange-2)', cursor: 'pointer', fontSize: 12.5 }}
            >
              Edit what to watch
            </button>
          </div>

          {/* Priority tier */}
          <Field label="Alert priority" hint="Affects how matches are labelled">
            <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
              {[
                { id: 'standard', title: 'Standard', desc: 'Normal alert handling for regular searches.' },
                { id: 'priority', title: 'Priority — for rare specs', desc: 'Highlighted in matches and notifications.' },
              ].map(opt => (
                <button
                  key={opt.id}
                  type="button"
                  onClick={() => setPriority(opt.id)}
                  style={{
                    display: 'flex', alignItems: 'flex-start', gap: 12,
                    padding: '14px 16px',
                    background: 'var(--bg)',
                    border: `1px solid ${priority === opt.id ? 'var(--orange)' : 'var(--line)'}`,
                    borderRadius: 10,
                    cursor: 'pointer',
                    textAlign: 'left',
                    transition: 'all 0.12s ease',
                  }}
                >
                  <div style={{
                    width: 18, height: 18, borderRadius: '50%',
                    border: `2px solid ${priority === opt.id ? 'var(--orange)' : 'var(--line-2)'}`,
                    flexShrink: 0,
                    marginTop: 1,
                    position: 'relative',
                  }}>
                    {priority === opt.id && (
                      <div style={{
                        position: 'absolute',
                        inset: 3,
                        background: 'var(--orange)',
                        borderRadius: '50%',
                      }} />
                    )}
                  </div>
                  <div style={{ flex: 1 }}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 3 }}>
                      <span style={{ fontSize: 14, fontWeight: 500 }}>{opt.title}</span>
                      {opt.id === 'priority' && (
                        <span style={{
                          fontFamily: 'var(--font-mono)',
                          fontSize: 10, fontWeight: 600, letterSpacing: '0.06em',
                          padding: '2px 6px',
                          background: 'var(--orange-soft)',
                          color: 'var(--orange-2)',
                          borderRadius: 3,
                        }}>INSTANT</span>
                      )}
                    </div>
                    <div style={{ fontSize: 12.5, color: 'var(--ink-3)' }}>{opt.desc}</div>
                  </div>
                </button>
              ))}
            </div>
          </Field>

          {/* Notify */}
          <Field label="Notify me by">
            <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
              {[
                { key: 'in_app', label: 'In-app inbox', hint: 'Bell notifications inside Saddle Alert' },
                {
                  key: 'telegram',
                  label: 'Telegram',
                  hint: channelStatus?.telegram?.enabled && channelStatus?.telegram?.connected
                    ? 'Connected to your Telegram'
                    : channelStatus?.telegram?.botConfigured
                      ? 'Connect Telegram in Settings first'
                      : 'Telegram bot is not configured yet',
                  disabled: channelStatus?.telegram && !notifyChannels.includes('telegram') && (!channelStatus.telegram.enabled || !channelStatus.telegram.connected),
                },
              ].map(opt => (
                <div key={opt.key} style={{
                  display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                  padding: '12px 14px',
                  background: 'var(--bg)',
                  border: '1px solid var(--line)',
                  borderRadius: 10,
                }}>
                  <div>
                    <div style={{ fontSize: 14, fontWeight: 500 }}>{opt.label}</div>
                    <div style={{ fontSize: 12, color: 'var(--ink-3)' }}>{opt.hint}</div>
                  </div>
                  <Toggle
                    value={notifyChannels.includes(opt.key)}
                    onChange={() => toggleNotifyChannel(opt.key)}
                    disabled={opt.disabled}
                  />
                </div>
              ))}
            </div>
          </Field>

          {/* Alert quality */}
          <div style={{
            display: 'grid',
            gap: 10,
            padding: '14px 16px',
            background: 'var(--bg)',
            border: '1px solid var(--line)',
            borderRadius: 10,
          }}>
            <div style={{ fontSize: 13, fontWeight: 600 }}>Alert quality</div>
            <div style={{ display: 'grid', gap: 8 }}>
              {alertQualityHints.map((hint, index) => {
                const color = hint.tone === 'good'
                  ? 'var(--green)'
                  : hint.tone === 'warn'
                    ? 'var(--orange)'
                    : 'var(--ink-3)';
                return (
                  <div key={`${hint.tone}-${index}`} style={{ display: 'flex', alignItems: 'flex-start', gap: 9, fontSize: 12.5, color: 'var(--ink-2)' }}>
                    <span style={{
                      width: 8,
                      height: 8,
                      marginTop: 5,
                      borderRadius: '50%',
                      background: color,
                      flexShrink: 0,
                    }} />
                    <span>{hint.text}</span>
                  </div>
                );
              })}
            </div>
          </div>

          {/* Current snapshot test */}
          <div style={{
            padding: '14px 16px',
            background: 'var(--bg)',
            border: '1px dashed var(--line-2)',
            borderRadius: 10,
            display: 'flex', gap: 10, alignItems: 'flex-start',
          }}>
            <div style={{ width: 4, alignSelf: 'stretch', background: 'var(--orange)', borderRadius: 4 }} />
            <div style={{ flex: 1 }}>
              <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, marginBottom: 4 }}>
                <div style={{ fontSize: 11, color: 'var(--ink-3)', textTransform: 'uppercase', fontFamily: 'var(--font-mono)' }}>
                  Current snapshot test
                </div>
                <Btn size="sm" kind="secondary" onClick={loadLivePreview} disabled={livePreviewLoading}>
                  {livePreviewLoading ? 'Testing...' : 'Refresh test'}
                </Btn>
              </div>
              <div style={{ fontFamily: 'var(--font-serif)', fontSize: 17, color: 'var(--ink)' }}>
                a <em>{summary}</em> appears in {selectedMarketLabel} {priceCapped ? `under $${maxPrice.toLocaleString()}` : 'at any price'}.
              </div>
              <div style={{ marginTop: 12, display: 'flex', flexDirection: 'column', gap: 6 }}>
                {livePreviewError ? (
                  <div style={{ fontSize: 12, color: 'var(--red)' }}>{livePreviewError}</div>
                ) : livePreviewLoading && !livePreviewItems.length ? (
                  <div style={{ fontSize: 12, color: 'var(--ink-3)' }}>Checking the latest storefront snapshot...</div>
                ) : (
                  <div style={{ fontSize: 12, color: 'var(--ink-3)' }}>
                    <span className="num" style={{ color: previewMatches.length ? 'var(--orange-2)' : 'var(--ink-3)', fontWeight: 600 }}>
                      {previewMatches.length}
                    </span> current storefront item{previewMatches.length === 1 ? '' : 's'} match this alert
                    {!previewMatches.length && livePreviewItems.length > 0 ? ' right now' : ''}
                  </div>
                )}
                {previewMatches.slice(0, 3).map(item => (
                  <div key={item.url} style={{
                    display: 'grid',
                    gridTemplateColumns: '1fr auto',
                    gap: 10,
                    fontSize: 12,
                    color: 'var(--ink-2)',
                    padding: '6px 0',
                    borderTop: '1px solid var(--line)',
                  }}>
                    <span style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{item.title}</span>
                    <span className="mono">{formatPriceText(item.price, item.currency)}</span>
                  </div>
                ))}
                {!previewMatches.length && livePreviewItems.length > 0 && !livePreviewError && (
                  <div style={{ fontSize: 12, color: 'var(--ink-3)' }}>
                    Saving is still useful: the cloud checker will keep testing future AU stock.
                  </div>
                )}
              </div>
            </div>
          </div>
            </>
          )}

          {/* Actions */}
          <div style={{
            display: 'flex', gap: 10, justifyContent: 'flex-end', flexWrap: 'wrap-reverse',
            paddingTop: 8, borderTop: '1px solid var(--line)',
          }}>
            {step === 'target' && !targetReady && (
              <div style={{ flex: '1 1 240px', color: 'var(--red)', fontSize: 12.5 }}>
                Complete the required information: {missingTargetFields.join(', ')}.
              </div>
            )}
            <Btn kind="ghost" onClick={onCancel}>Cancel</Btn>
            {step === 'target' ? (
              <Btn kind="accent" icon="chevron-right" disabled={!targetReady} onClick={() => setStep('delivery')}>
                Continue
              </Btn>
            ) : (
              <>
                <Btn kind="secondary" onClick={() => setStep('target')}>Back</Btn>
                <Btn kind="accent" icon="check" disabled={!valid} onClick={saveAlert}>
                  {editingAlert ? 'Save changes' : 'Create alert'}
                </Btn>
              </>
            )}
          </div>
        </div>
      </div>
    </div>
  );
};

// ====================================================================
// ADMIN DASHBOARD
// ====================================================================
const AdminDashboardScreen = () => {
  const [summary, setSummary] = useState_s(null);
  const [events, setEvents] = useState_s([]);
  const [checkerSettings, setCheckerSettings] = useState_s(null);
  const [catalogTypes, setCatalogTypes] = useState_s([]);
  const [savingInterval, setSavingInterval] = useState_s(false);
  const [cleaningStarterAlerts, setCleaningStarterAlerts] = useState_s(false);
  const [starterCleanupResult, setStarterCleanupResult] = useState_s(null);
  const [disconnectingTelegramUserId, setDisconnectingTelegramUserId] = useState_s(null);
  const [loading, setLoading] = useState_s(true);
  const [error, setError] = useState_s(null);

  const loadAdmin = () => {
    setLoading(true);
    setError(null);
    Promise.all([
      fetch('/api/admin/summary').then(res => res.json().then(json => ({ ok: res.ok, json }))),
      fetch('/api/checker-events?limit=8').then(res => res.json().then(json => ({ ok: res.ok, json }))),
      fetch('/api/admin/checker-settings').then(res => res.json().then(json => ({ ok: res.ok, json }))),
      fetch('/api/catalog-types').then(res => res.json().then(json => ({ ok: res.ok, json }))),
    ])
      .then(([summaryRes, eventsRes, settingsRes, catalogRes]) => {
        if (!summaryRes.ok) throw new Error(summaryRes.json.error || 'Could not load admin summary');
        setSummary(summaryRes.json);
        setEvents(Array.isArray(eventsRes.json.events) ? eventsRes.json.events : []);
        if (settingsRes.ok) setCheckerSettings(settingsRes.json);
        if (catalogRes.ok) setCatalogTypes(Array.isArray(catalogRes.json.types) ? catalogRes.json.types : []);
      })
      .catch((err) => setError(err.message || 'Could not load admin dashboard'))
      .finally(() => setLoading(false));
  };

  useEffect_s(() => {
    loadAdmin();
  }, []);

  const totals = summary?.totals || {};
  const users = summary?.users || [];
  const deliveryRows = summary?.deliveries || [];
  const categoryHealth = Array.isArray(summary?.categoryHealth) ? summary.categoryHealth : [];
  const latestChecker = events.find(event => event.eventType === 'checker_finished') || events[0] || null;
  const pollEvents = events.filter(event => event.eventType !== 'checker_skipped' || event.reason !== 'interval_not_due');
  const heartbeatEvents = events.filter(event => event.eventType === 'checker_skipped' && event.reason === 'interval_not_due');
  const usersWithoutAlerts = users.filter(user => (user.alertCount || 0) === 0);
  const catalogCategoryCounts = PRODUCT_CATEGORIES
    .filter(category => category.key !== 'all')
    .map(category => ({
      ...category,
      count: catalogTypes.filter(type => (type.category || productCategoryForModel(type.model, type.title)) === category.key).length,
    }));
  const topCatalogTypes = [...catalogTypes]
    .sort((a, b) => (Number(b.observedCount || 0) - Number(a.observedCount || 0)) || String(a.model || '').localeCompare(String(b.model || '')))
    .slice(0, 12);
  const deliveryReasonLabel = (delivery) => {
    const reason = String(delivery?.error || '').trim();
    if (!reason) return delivery.status === 'sent' ? 'Sent to connected Telegram account' : 'No detail recorded';
    if (reason === 'telegram_not_selected') return 'Telegram was not selected on this alert';
    if (reason === 'not_configured') return 'Telegram bot or user chat is not configured';
    if (reason === 'bot_not_configured') return 'Telegram bot token is not configured';
    if (reason === 'user_not_connected') return 'The alert owner has not connected Telegram';
    if (reason === 'chat_not_configured') return 'No Telegram chat is configured';
    if (reason === 'disabled') return 'Telegram delivery is disabled';
    if (reason === 'detail_unverified') return 'Detail verification was not available';
    if (reason === 'blocked') return 'Detail check was blocked';
    return reason.replace(/_/g, ' ');
  };
  const categoryHealthTone = (entry) => (
    entry.status === 'ok' && Number(entry.itemCount || 0) > 0
      ? 'ok'
      : entry.status === 'missing' || entry.runStatus === 'parse_error' || entry.runStatus === 'browser_error'
        ? 'bad'
        : 'warn'
  );
  const categoryHealthColor = (tone) => (
    tone === 'ok' ? 'var(--green)' : tone === 'bad' ? 'var(--red)' : 'var(--orange-2)'
  );
  const categoryHealthDetail = (entry) => {
    if (!entry.runId) return entry.error || 'No snapshot yet';
    const checkedAt = entry.finishedAt || entry.startedAt;
    const time = checkedAt ? new Date(checkedAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : '—';
    if (entry.status === 'ok' && Number(entry.itemCount || 0) > 0) return `Latest run · ${time}`;
    return `${entry.error || entry.runStatus || 'Needs attention'} · ${time}`;
  };
  const statItems = [
    { label: 'Users', value: totals.users ?? 0 },
    { label: 'Alerts', value: totals.alerts ?? 0 },
    { label: 'Matches', value: totals.matches ?? 0 },
    { label: 'Notifications', value: totals.notifications ?? 0 },
  ];

  const updateCheckerInterval = (intervalMinutes) => {
    setSavingInterval(true);
    fetch('/api/admin/checker-settings', {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ intervalMinutes }),
    })
      .then(res => res.json().then(json => ({ ok: res.ok, json })))
      .then(({ ok, json }) => {
        if (!ok) throw new Error(json.error || 'Could not save checker interval');
        setCheckerSettings(json);
      })
      .catch((err) => setError(err.message || 'Could not save checker interval'))
      .finally(() => setSavingInterval(false));
  };

  const cleanupStarterAlerts = () => {
    setCleaningStarterAlerts(true);
    setStarterCleanupResult(null);
    fetch('/api/admin/starter-alerts/cleanup', { method: 'POST' })
      .then(res => res.json().then(json => ({ ok: res.ok, json })))
      .then(({ ok, json }) => {
        if (!ok) throw new Error(json.error || 'Could not remove starter alerts');
        setStarterCleanupResult(json);
        loadAdmin();
      })
      .catch((err) => setError(err.message || 'Could not remove starter alerts'))
      .finally(() => setCleaningStarterAlerts(false));
  };

  const disconnectUserTelegram = (targetUser) => {
    if (!targetUser?.id || !targetUser.telegramConnected) return;
    setDisconnectingTelegramUserId(targetUser.id);
    setError(null);
    fetch(`/api/admin/users/${encodeURIComponent(targetUser.id)}/telegram`, { method: 'DELETE' })
      .then(res => res.json().then(json => ({ ok: res.ok, json })))
      .then(({ ok, json }) => {
        if (!ok) throw new Error(json.error || 'Could not disconnect Telegram');
        setSummary(current => {
          if (!current) return current;
          const users = (current.users || []).map(user => (
            user.id === targetUser.id
              ? { ...user, telegramConnected: false, telegramUsername: null }
              : user
          ));
          const connectedCount = users.filter(user => user.telegramConnected).length;
          return {
            ...current,
            users,
            totals: {
              ...(current.totals || {}),
              telegramConnectedUsers: connectedCount,
            },
          };
        });
      })
      .catch((err) => setError(err.message || 'Could not disconnect Telegram'))
      .finally(() => setDisconnectingTelegramUserId(null));
  };

  return (
    <div>
      <SectionHead
        title="Admin"
        subtitle="System activity, users, alerts, and checker health."
        action={<Btn kind="secondary" onClick={loadAdmin} disabled={loading}>{loading ? 'Loading...' : 'Refresh'}</Btn>}
      />

      {error && (
        <div style={{
          color: 'var(--red)',
          background: '#FBEAEA',
          border: '1px solid #F0C9C9',
          borderRadius: 8,
          padding: '10px 12px',
          fontSize: 13,
          marginBottom: 14,
        }}>{error}</div>
      )}

      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))', gap: 10, marginBottom: 16 }}>
        {statItems.map(item => (
          <div key={item.label} style={{
            background: 'var(--bg-elev)',
            border: '1px solid var(--line)',
            borderRadius: 10,
            padding: '14px 16px',
          }}>
            <div className="mono" style={{ fontSize: 11, color: 'var(--ink-3)', textTransform: 'uppercase' }}>{item.label}</div>
            <div className="num" style={{ fontSize: 28, color: 'var(--ink)', marginTop: 6 }}>{item.value}</div>
          </div>
        ))}
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 10, marginBottom: 14 }}>
        {[
          {
            title: 'Telegram health',
            value: `${totals.telegramConnectedUsers || 0}/${totals.users || 0}`,
            detail: `${totals.telegramAlertUsersMissingConnection || 0} users need connection`,
            tone: (totals.telegramAlertUsersMissingConnection || 0) > 0 ? 'warn' : 'ok',
          },
          {
            title: 'Checker health',
            value: latestChecker?.status || '—',
            detail: latestChecker ? `${latestChecker.message} · ${new Date(latestChecker.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}` : 'No checker events yet',
            tone: latestChecker?.status === 'ok' ? 'ok' : latestChecker?.status === 'failed' ? 'bad' : 'neutral',
          },
          {
            title: 'User readiness',
            value: `${usersWithoutAlerts.length}`,
            detail: 'users without alerts',
            tone: usersWithoutAlerts.length > 0 ? 'warn' : 'ok',
          },
          {
            title: 'Active alerts',
            value: totals.activeAlerts ?? 0,
            detail: `${totals.telegramAlerts || 0} with Telegram`,
            tone: 'neutral',
          },
        ].map(card => {
          const color = card.tone === 'ok' ? 'var(--green)' : card.tone === 'bad' ? 'var(--red)' : card.tone === 'warn' ? 'var(--orange-2)' : 'var(--ink)';
          const bg = card.tone === 'ok' ? '#E6F2EC' : card.tone === 'bad' ? '#FBEAEA' : card.tone === 'warn' ? 'var(--orange-soft)' : 'var(--bg-elev)';
          return (
            <div key={card.title} style={{
              background: 'var(--bg-elev)',
              border: '1px solid var(--line)',
              borderRadius: 10,
              padding: 14,
            }}>
              <div className="mono" style={{ fontSize: 11, color: 'var(--ink-3)', textTransform: 'uppercase' }}>{card.title}</div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 8 }}>
                <span className="num" style={{ fontSize: 22, color }}>{card.value}</span>
                <span style={{ width: 9, height: 9, borderRadius: '50%', background: bg, border: `1px solid ${color}` }} />
              </div>
              <div style={{ fontSize: 12, color: 'var(--ink-3)', marginTop: 4, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{card.detail}</div>
            </div>
          );
        })}
      </div>

      <div style={{
        background: 'var(--bg-elev)',
        border: '1px solid var(--line)',
        borderRadius: 10,
        padding: 16,
        marginBottom: 14,
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'space-between',
        gap: 14,
        flexWrap: 'wrap',
      }}>
        <div>
          <div style={{ fontSize: 14, fontWeight: 600 }}>Cloud checker</div>
          <div style={{ fontSize: 12.5, color: 'var(--ink-3)', marginTop: 3 }}>
            Supabase heartbeat wakes every 5 minutes; real source polls run only when this interval is due.
          </div>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <select
            value={checkerSettings?.intervalMinutes || 30}
            disabled={savingInterval}
            onChange={(e) => updateCheckerInterval(Number(e.target.value))}
            style={{
              height: 34,
              border: '1px solid var(--line-2)',
              borderRadius: 8,
              background: 'var(--bg-elev)',
              color: 'var(--ink-2)',
              fontSize: 13,
              padding: '0 9px',
            }}
          >
            <option value="30">Every 30 minutes</option>
            <option value="60">Every hour</option>
            <option value="120">Every 2 hours</option>
            <option value="360">Every 6 hours</option>
          </select>
          <span className="mono" style={{ color: 'var(--ink-3)', fontSize: 11 }}>
            {savingInterval ? 'Saving' : checkerSettings?.lastRunAt ? `Last ${new Date(checkerSettings.lastRunAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}` : 'No runs yet'}
          </span>
        </div>
      </div>

      <div style={{
        background: 'var(--bg-elev)',
        border: '1px solid var(--line)',
        borderRadius: 10,
        padding: 16,
        marginBottom: 14,
      }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', gap: 12, alignItems: 'flex-start', marginBottom: 12 }}>
          <div>
            <div style={{ fontSize: 14, fontWeight: 600 }}>Storefront category health</div>
            <div style={{ fontSize: 12.5, color: 'var(--ink-3)', marginTop: 3 }}>
              Shows whether each configured storefront category was captured in the latest usable run.
            </div>
          </div>
          <span className="mono" style={{ color: 'var(--ink-3)', fontSize: 11 }}>
            {categoryHealth.filter(entry => categoryHealthTone(entry) === 'ok').length}/{categoryHealth.length || 0} ok
          </span>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(210px, 1fr))', gap: 8 }}>
          {categoryHealth.map(entry => {
            const tone = categoryHealthTone(entry);
            const color = categoryHealthColor(tone);
            return (
              <div key={`${entry.region}-${entry.category}`} style={{
                border: `1px solid ${tone === 'ok' ? 'var(--line)' : color}`,
                borderRadius: 8,
                padding: '10px 11px',
                background: tone === 'ok' ? 'var(--bg)' : tone === 'bad' ? '#FBEAEA' : 'var(--orange-soft)',
                minWidth: 0,
              }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8 }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 7, minWidth: 0 }}>
                    <RegionFlags code={entry.region} size={14} />
                    <span style={{ fontSize: 13, fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                      {entry.region} · {entry.label || categoryLabel(entry.category)}
                    </span>
                  </div>
                  <span className="mono" style={{ color, fontSize: 11, textTransform: 'uppercase' }}>
                    {tone === 'ok' ? 'ok' : tone === 'bad' ? 'check' : 'warn'}
                  </span>
                </div>
                <div style={{ display: 'flex', alignItems: 'baseline', gap: 6, marginTop: 8 }}>
                  <span className="num" style={{ fontSize: 20, color: 'var(--ink)' }}>{Number(entry.itemCount || 0).toLocaleString()}</span>
                  <span style={{ fontSize: 11.5, color: 'var(--ink-3)' }}>items</span>
                </div>
                <div style={{ fontSize: 11.5, color: tone === 'bad' ? 'var(--red)' : 'var(--ink-3)', marginTop: 4, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                  {categoryHealthDetail(entry)}
                </div>
              </div>
            );
          })}
          {!loading && categoryHealth.length === 0 && (
            <div style={{ color: 'var(--ink-3)', fontSize: 13 }}>No category health data yet.</div>
          )}
        </div>
      </div>

      <div style={{
        background: 'var(--bg-elev)',
        border: '1px solid var(--line)',
        borderRadius: 10,
        padding: 16,
        marginBottom: 14,
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'space-between',
        gap: 14,
        flexWrap: 'wrap',
      }}>
        <div>
          <div style={{ fontSize: 14, fontWeight: 600 }}>Starter alerts</div>
          <div style={{ fontSize: 12.5, color: 'var(--ink-3)', marginTop: 3 }}>
            Remove old auto-created starter alerts.
          </div>
          {starterCleanupResult && (
            <div className="mono" style={{ color: 'var(--green)', fontSize: 11, marginTop: 6 }}>
              Removed {starterCleanupResult.deletedAlerts || 0} alerts, {starterCleanupResult.deletedMatches || 0} matches, {starterCleanupResult.deletedNotifications || 0} notices
            </div>
          )}
        </div>
        <Btn kind="secondary" onClick={cleanupStarterAlerts} disabled={cleaningStarterAlerts}>
          {cleaningStarterAlerts ? 'Removing...' : 'Remove starter alerts'}
        </Btn>
      </div>

      <div style={{
        background: 'var(--bg-elev)',
        border: '1px solid var(--line)',
        borderRadius: 10,
        padding: 16,
        marginBottom: 14,
      }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', gap: 12, alignItems: 'flex-start', marginBottom: 12 }}>
          <div>
            <div style={{ fontSize: 14, fontWeight: 600 }}>Captured catalog</div>
            <div style={{ fontSize: 12.5, color: 'var(--ink-3)', marginTop: 3 }}>
              Types available to the alert builder from storefront history.
            </div>
          </div>
          <span className="mono" style={{ color: 'var(--ink-3)', fontSize: 11 }}>{catalogTypes.length} types</span>
        </div>
        <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 12 }}>
          {catalogCategoryCounts.map(category => (
            <Chip key={category.key} size="sm" accent active={category.count > 0}>
              {category.label} {category.count}
            </Chip>
          ))}
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 8 }}>
          {topCatalogTypes.map(type => (
            <div key={type.model} style={{
              padding: '9px 10px',
              border: '1px solid var(--line)',
              borderRadius: 8,
              background: 'var(--bg)',
              minWidth: 0,
            }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, alignItems: 'center' }}>
                <span style={{ fontSize: 13, fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{type.model}</span>
                <span className="mono" style={{ color: 'var(--ink-3)', fontSize: 10 }}>{categoryLabel(type.category || productCategoryForModel(type.model, type.title))}</span>
              </div>
              <div style={{ fontSize: 11.5, color: 'var(--ink-3)', marginTop: 4, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                {Number(type.observedCount || 0).toLocaleString()} observations
                {Array.isArray(type.sizes) && type.sizes.length ? ` · sizes ${type.sizes.join(', ')}` : ''}
              </div>
            </div>
          ))}
          {!loading && topCatalogTypes.length === 0 && (
            <div style={{ color: 'var(--ink-3)', fontSize: 13 }}>No catalog types captured yet.</div>
          )}
        </div>
      </div>

      <div className="admin-grid" style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1.35fr) minmax(280px, 0.65fr)', gap: 14 }}>
        <div style={{
          background: 'var(--bg-elev)',
          border: '1px solid var(--line)',
          borderRadius: 10,
          padding: 16,
          minWidth: 0,
        }}>
          <div style={{ fontSize: 14, fontWeight: 600, marginBottom: 10 }}>Users</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            {users.map(user => (
              <div key={user.id} style={{
                display: 'grid',
                gridTemplateColumns: 'minmax(0, 1fr) auto auto auto auto auto',
                gap: 10,
                alignItems: 'center',
                padding: '10px 0',
                borderTop: '1px solid var(--line)',
                fontSize: 12.5,
              }}>
                <div style={{ minWidth: 0 }}>
                  <div style={{ fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                    {user.name || user.email}
                    {user.isAdmin && <span style={{ marginLeft: 6, color: 'var(--green)', fontFamily: 'var(--font-mono)', fontSize: 10 }}>ADMIN</span>}
                  </div>
                  <div style={{ color: 'var(--ink-3)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{user.email}</div>
                </div>
                <span className="mono" style={{ color: user.telegramConnected ? 'var(--green)' : (user.telegramAlertCount || 0) > 0 ? 'var(--orange-2)' : 'var(--ink-3)' }}>
                  {user.telegramConnected
                    ? (user.telegramUsername ? `@${user.telegramUsername}` : 'telegram')
                    : (user.telegramAlertCount || 0) > 0 ? 'needs tg' : 'no tg'}
                </span>
                <span className="mono" style={{ color: 'var(--ink-3)' }}>{user.alertCount || 0} alerts</span>
                <span className="mono" style={{ color: 'var(--ink-3)' }}>{user.matchCount || 0} matches</span>
                <span className="mono" style={{ color: 'var(--ink-3)' }}>{user.notificationCount || 0} notices</span>
                <Btn
                  size="sm"
                  kind="ghost"
                  onClick={() => disconnectUserTelegram(user)}
                  disabled={!user.telegramConnected || disconnectingTelegramUserId === user.id}
                >
                  {disconnectingTelegramUserId === user.id ? 'Disconnecting...' : 'Disconnect TG'}
                </Btn>
              </div>
            ))}
            {!loading && users.length === 0 && (
              <div style={{ color: 'var(--ink-3)', fontSize: 13 }}>No users yet.</div>
            )}
          </div>
        </div>

        <div style={{
          background: 'var(--bg-elev)',
          border: '1px solid var(--line)',
          borderRadius: 10,
          padding: 16,
          minWidth: 0,
        }}>
          <div style={{ fontSize: 14, fontWeight: 600, marginBottom: 10 }}>Checker history</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            <div className="mono" style={{ color: 'var(--ink-3)', fontSize: 11, textTransform: 'uppercase', letterSpacing: '0.06em' }}>
              Real polls
            </div>
            {pollEvents.map(event => (
              <div key={event.id} style={{ borderTop: '1px solid var(--line)', paddingTop: 8 }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, fontSize: 12 }}>
                  <span className="mono" style={{ color: event.status === 'ok' ? 'var(--green)' : event.status === 'failed' ? 'var(--red)' : 'var(--ink-3)', textTransform: 'uppercase' }}>
                    {event.status}
                  </span>
                  <span className="mono" style={{ color: 'var(--ink-3)' }}>
                    {new Date(event.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
                  </span>
                </div>
                <div style={{ color: 'var(--ink-2)', fontSize: 12.5, marginTop: 4 }}>{event.message}</div>
              </div>
            ))}
            {!loading && pollEvents.length === 0 && (
              <div style={{ color: 'var(--ink-3)', fontSize: 13 }}>No checker events yet.</div>
            )}
            <div className="mono" style={{ color: 'var(--ink-3)', fontSize: 11, textTransform: 'uppercase', letterSpacing: '0.06em', marginTop: 8 }}>
              Cron heartbeat logs
            </div>
            {heartbeatEvents.map(event => (
              <div key={event.id} style={{ borderTop: '1px solid var(--line)', paddingTop: 8 }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, fontSize: 12 }}>
                  <span className="mono" style={{ color: 'var(--orange-2)', textTransform: 'uppercase' }}>
                    skipped
                  </span>
                  <span className="mono" style={{ color: 'var(--ink-3)' }}>
                    {new Date(event.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
                  </span>
                </div>
                <div style={{ color: 'var(--ink-3)', fontSize: 12.5, marginTop: 4 }}>{event.message}</div>
              </div>
            ))}
            {!loading && heartbeatEvents.length === 0 && (
              <div style={{ color: 'var(--ink-3)', fontSize: 13 }}>No heartbeat skips logged.</div>
            )}
          </div>
        </div>

        <div style={{
          background: 'var(--bg-elev)',
          border: '1px solid var(--line)',
          borderRadius: 10,
          padding: 16,
          minWidth: 0,
        }}>
          <div style={{ fontSize: 14, fontWeight: 600, marginBottom: 10 }}>Delivery diagnostics</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            {deliveryRows.map((delivery, idx) => {
              const ok = delivery.status === 'sent';
              const failed = delivery.status === 'failed';
              return (
                <div key={`${delivery.createdAt}-${idx}`} style={{ borderTop: '1px solid var(--line)', paddingTop: 8 }}>
                  <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, fontSize: 12 }}>
                    <span className="mono" style={{ color: ok ? 'var(--green)' : failed ? 'var(--red)' : 'var(--orange-2)', textTransform: 'uppercase' }}>
                      {delivery.channel} · {delivery.status}
                    </span>
                    <span className="mono" style={{ color: 'var(--ink-3)' }}>
                      {new Date(delivery.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
                    </span>
                  </div>
                  {(delivery.alertName || delivery.ownerEmail || delivery.notificationTitle) && (
                    <div style={{ color: 'var(--ink)', fontSize: 12.5, marginTop: 4, overflowWrap: 'anywhere' }}>
                      {delivery.alertName || delivery.notificationTitle || 'Notification'}
                      {delivery.ownerEmail ? <span style={{ color: 'var(--ink-3)' }}> · {delivery.ownerEmail}</span> : null}
                    </div>
                  )}
                  {(delivery.error || ok) && (
                    <div style={{ color: failed ? 'var(--red)' : 'var(--ink-3)', fontSize: 12.5, marginTop: 4, overflowWrap: 'anywhere' }}>
                      {deliveryReasonLabel(delivery)}
                    </div>
                  )}
                </div>
              );
            })}
            {!loading && deliveryRows.length === 0 && (
              <div style={{ color: 'var(--ink-3)', fontSize: 13 }}>No delivery attempts yet.</div>
            )}
          </div>
        </div>
      </div>
    </div>
  );
};

// ====================================================================
// SETTINGS SCREEN
// ====================================================================
const SettingsScreen = ({ user, activeRegions, setActiveRegions, enabledRegions, notif, setNotif, onSignOut }) => {
  const [channels, setChannels] = useState_s(null);
  const [testingTelegram, setTestingTelegram] = useState_s(false);
  const [telegramResult, setTelegramResult] = useState_s(null);
  const [telegramConnectCode, setTelegramConnectCode] = useState_s(null);
  const [creatingTelegramCode, setCreatingTelegramCode] = useState_s(false);
  const [disconnectingTelegram, setDisconnectingTelegram] = useState_s(false);

  const loadChannels = () => {
    fetch('/api/notification-channels')
      .then(res => res.json())
      .then(json => setChannels(json))
      .catch(() => {});
  };

  useEffect_s(() => {
    loadChannels();
  }, []);

  const testTelegram = () => {
    setTestingTelegram(true);
    setTelegramResult(null);
    fetch('/api/notification-channels/telegram/test', { method: 'POST' })
      .then(res => res.json().then(json => ({ ok: res.ok, json })))
      .then(({ ok, json }) => {
        setTelegramResult({
          ok,
          text: ok
            ? 'Telegram test sent'
            : json.reason === 'disabled'
              ? 'Telegram is disabled'
              : json.reason === 'not_configured'
                ? 'Telegram is not configured'
                : json.error || 'Telegram test failed'
        });
        loadChannels();
      })
      .catch(() => setTelegramResult({ ok: false, text: 'Telegram test failed' }))
      .finally(() => setTestingTelegram(false));
  };

  const createTelegramCode = () => {
    setCreatingTelegramCode(true);
    setTelegramResult(null);
    fetch('/api/notification-channels/telegram/connect-code', { method: 'POST' })
      .then(res => res.json().then(json => ({ ok: res.ok, json })))
      .then(({ ok, json }) => {
        if (!ok) throw new Error(json.error || 'Could not create Telegram code');
        setTelegramConnectCode(json);
      })
      .catch((err) => setTelegramResult({ ok: false, text: err.message || 'Could not create Telegram code' }))
      .finally(() => setCreatingTelegramCode(false));
  };

  const disconnectTelegram = () => {
    setDisconnectingTelegram(true);
    setTelegramResult(null);
    fetch('/api/notification-channels/telegram', { method: 'DELETE' })
      .then(res => res.json().then(json => ({ ok: res.ok, json })))
      .then(({ ok }) => {
        if (!ok) throw new Error('Could not disconnect Telegram');
        setTelegramConnectCode(null);
        setTelegramResult({ ok: true, text: 'Telegram disconnected' });
        loadChannels();
      })
      .catch((err) => setTelegramResult({ ok: false, text: err.message || 'Could not disconnect Telegram' }))
      .finally(() => setDisconnectingTelegram(false));
  };

  const telegramStartUrl = telegramConnectCode?.code && channels?.telegram?.botUsername
    ? `https://t.me/${channels.telegram.botUsername}?start=${encodeURIComponent(telegramConnectCode.code)}`
    : null;
  const telegramQrUrl = telegramStartUrl
    ? `https://api.qrserver.com/v1/create-qr-code/?size=176x176&margin=8&data=${encodeURIComponent(telegramStartUrl)}`
    : null;

  const toggleRegion = (code) => {
    if (!enabledRegions.includes(code)) return;
    setActiveRegions(activeRegions.includes(code)
      ? activeRegions.filter(c => c !== code)
      : [...activeRegions, code]);
  };

  return (
    <div>
      <SectionHead title="Settings" subtitle="Defaults for your account" />

      <div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
        {/* Account */}
        <SettingsCard title="Account">
          <SettingRow
            label="Email"
            description="Where alerts are sent"
            right={<span className="mono" style={{ fontSize: 13, color: 'var(--ink-2)' }}>{user?.email || 'Not signed in'}</span>}
          />
          <SettingRow
            label="Account"
            description="Local staging account"
            right={<span style={{ fontSize: 13, color: 'var(--ink-2)' }}>{user?.name || 'Saddle Alert user'}</span>}
          />
          <SettingRow
            label="Access"
            description={user?.isAdmin ? 'Can see system status and all user activity' : 'Personal account view'}
            right={
              <span style={{
                display: 'inline-flex',
                alignItems: 'center',
                gap: 6,
                fontSize: 11.5,
                fontFamily: 'var(--font-mono)',
                textTransform: 'uppercase',
                color: user?.isAdmin ? 'var(--green)' : 'var(--ink-3)',
                background: user?.isAdmin ? '#E6F2EC' : 'var(--bg)',
                border: '1px solid var(--line-2)',
                borderRadius: 999,
                padding: '4px 9px',
              }}>
                {user?.isAdmin ? 'Admin' : 'Standard'}
              </span>
            }
          />
          <SettingRow
            label="Time zone"
            description="For timestamps"
            right={<span style={{ fontSize: 13, color: 'var(--ink-2)' }}>Australia / Sydney</span>}
          />
        </SettingsCard>

        {/* Regions */}
        <SettingsCard
          title="Default regions"
          subtitle="Only these stores are checked. You can override per alert."
        >
          <div style={{ display: 'flex', flexDirection: 'column', gap: 0 }}>
            {window.DW_DATA.REGIONS.map((r, i) => (
              <div key={r.code} style={{
                display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                padding: '14px 0',
                borderTop: i === 0 ? 'none' : '1px solid var(--line)',
                opacity: enabledRegions.includes(r.code) ? 1 : 0.4,
              }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
                  <RegionFlags code={r.code} size={18} />
                  <div>
                    <div style={{ fontSize: 14, fontWeight: 500 }}>{r.name}</div>
                    <div style={{ fontSize: 12, color: 'var(--ink-3)' }} className="mono">{r.displayCode || r.code} · {r.currency}</div>
                  </div>
                </div>
                <Toggle value={activeRegions.includes(r.code)} onChange={() => toggleRegion(r.code)} />
              </div>
            ))}
          </div>
        </SettingsCard>

        {/* Notifications */}
        <SettingsCard title="Notification preferences">
          {[
            { key: 'push', label: 'Push notifications', desc: 'Browser + mobile' },
            { key: 'email', label: 'Email digest', desc: 'Daily summary at 9am' },
            { key: 'sms', label: 'SMS alerts', desc: 'For top-priority matches only' },
            { key: 'sound', label: 'Sound on new match', desc: 'Plays a soft chime' },
          ].map((opt, i) => (
            <div key={opt.key} style={{
              display: 'flex', alignItems: 'center', justifyContent: 'space-between',
              padding: '14px 0',
              borderTop: i === 0 ? 'none' : '1px solid var(--line)',
            }}>
              <div>
                <div style={{ fontSize: 14, fontWeight: 500 }}>{opt.label}</div>
                <div style={{ fontSize: 12, color: 'var(--ink-3)' }}>{opt.desc}</div>
              </div>
              <Toggle value={notif[opt.key]} onChange={(v) => setNotif({ ...notif, [opt.key]: v })} />
            </div>
          ))}
          <div style={{
            display: 'flex', alignItems: 'center', justifyContent: 'space-between',
            padding: '14px 0',
            borderTop: '1px solid var(--line)',
            gap: 14,
            flexWrap: 'wrap',
          }}>
            <div style={{ minWidth: 0, flex: '1 1 240px' }}>
              <div style={{ fontSize: 14, fontWeight: 500 }}>Telegram</div>
              <div style={{ fontSize: 12, color: 'var(--ink-3)' }}>
                {channels?.telegram?.connected
                  ? `Connected${channels.telegram.user?.username ? ` as @${channels.telegram.user.username}` : ''}`
                  : channels?.telegram?.botConfigured
                    ? 'Connect your Telegram account to receive alert messages'
                    : 'Admin needs to set the Telegram bot token first'}
              </div>
              {telegramConnectCode && (
                <div style={{
                  marginTop: 10,
                  padding: '12px',
                  border: '1px solid var(--line)',
                  borderRadius: 8,
                  background: 'var(--bg)',
                  fontSize: 12.5,
                  color: 'var(--ink-2)',
                }}>
                  <div style={{ display: 'flex', gap: 14, alignItems: 'center', flexWrap: 'wrap' }}>
                    {telegramQrUrl && (
                      <a href={telegramStartUrl} target="_blank" rel="noreferrer" style={{
                        display: 'inline-flex',
                        padding: 8,
                        border: '1px solid var(--line)',
                        borderRadius: 8,
                        background: 'var(--bg-elev)',
                      }}>
                        <img
                          src={telegramQrUrl}
                          alt="Telegram connect QR code"
                          width="132"
                          height="132"
                          style={{ display: 'block' }}
                        />
                      </a>
                    )}
                    <div style={{ minWidth: 0, flex: '1 1 180px' }}>
                      <div>Scan the QR code or send this code to the Telegram bot:</div>
                      <div className="mono" style={{ fontSize: 20, color: 'var(--ink)', marginTop: 5 }}>{telegramConnectCode.code}</div>
                      <div style={{ color: 'var(--ink-3)', marginTop: 5 }}>
                        {telegramStartUrl
                          ? 'Scanning opens Telegram with the connection code ready.'
                          : `Open your Saddle Alert bot and send /start ${telegramConnectCode.code}`}
                      </div>
                      {telegramStartUrl && (
                        <a href={telegramStartUrl} target="_blank" rel="noreferrer" style={{
                          display: 'inline-flex',
                          marginTop: 9,
                          color: 'var(--orange)',
                          fontWeight: 600,
                          textDecoration: 'none',
                        }}>
                          Open Telegram
                        </a>
                      )}
                    </div>
                  </div>
                </div>
              )}
              {telegramResult && (
                <div style={{
                  fontSize: 12,
                  color: telegramResult.ok ? 'var(--green)' : 'var(--orange-2)',
                  marginTop: 4,
                }}>{telegramResult.text}</div>
              )}
            </div>
            <div style={{
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'flex-start',
              gap: 8,
              flex: '1 1 220px',
              flexWrap: 'wrap',
              minWidth: 0,
            }}>
              <span style={{
                fontSize: 11.5,
                fontFamily: 'var(--font-mono)',
                textTransform: 'uppercase',
                color: channels?.telegram?.enabled && channels?.telegram?.connected ? 'var(--green)' : 'var(--ink-3)',
                background: channels?.telegram?.enabled && channels?.telegram?.connected ? '#E6F2EC' : 'var(--bg)',
                border: '1px solid var(--line-2)',
                borderRadius: 999,
                padding: '3px 8px',
              }}>
                {channels?.telegram?.enabled && channels?.telegram?.connected ? 'Connected' : 'Off'}
              </span>
              {channels?.telegram?.connected ? (
                <Btn size="sm" kind="ghost" onClick={disconnectTelegram} disabled={disconnectingTelegram}>
                  {disconnectingTelegram ? 'Disconnecting...' : 'Disconnect'}
                </Btn>
              ) : (
                <Btn size="sm" kind="secondary" onClick={createTelegramCode} disabled={creatingTelegramCode || !channels?.telegram?.botConfigured}>
                  {creatingTelegramCode ? 'Creating...' : 'Connect'}
                </Btn>
              )}
              <Btn size="sm" kind="secondary" onClick={testTelegram} disabled={testingTelegram}>
                {testingTelegram ? 'Testing...' : 'Test'}
              </Btn>
            </div>
          </div>
          {channels?.deliveries?.length > 0 && (
            <div style={{ borderTop: '1px solid var(--line)', paddingTop: 12 }}>
              <div style={{
                fontSize: 11.5,
                color: 'var(--ink-3)',
                fontFamily: 'var(--font-mono)',
                textTransform: 'uppercase',
                letterSpacing: '0.06em',
                marginBottom: 8,
              }}>Delivery log</div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
                {channels.deliveries.slice(0, 5).map((delivery, idx) => {
                  const ok = delivery.status === 'sent';
                  const failed = delivery.status === 'failed';
                  return (
                    <div key={`${delivery.createdAt}-${idx}`} style={{
                      display: 'grid',
                      gridTemplateColumns: 'auto minmax(0, 1fr) auto',
                      gap: 8,
                      alignItems: 'center',
                      fontSize: 12,
                    }}>
                      <span style={{
                        padding: '2px 7px',
                        borderRadius: 999,
                        background: ok ? '#E6F2EC' : failed ? '#FBEAEA' : 'var(--orange-soft)',
                        color: ok ? 'var(--green)' : failed ? 'var(--red)' : 'var(--orange-2)',
                        fontSize: 10.5,
                        fontFamily: 'var(--font-mono)',
                        textTransform: 'uppercase',
                      }}>{delivery.status}</span>
                      <span style={{
                        color: 'var(--ink-2)',
                        whiteSpace: 'nowrap',
                        overflow: 'hidden',
                        textOverflow: 'ellipsis',
                      }}>{delivery.error || delivery.channel}</span>
                      <span style={{ color: 'var(--ink-3)', fontFamily: 'var(--font-mono)', fontSize: 11 }}>
                        {new Date(delivery.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
                      </span>
                    </div>
                  );
                })}
              </div>
            </div>
          )}
        </SettingsCard>

        <div style={{ display: 'flex', gap: 10, paddingTop: 4 }}>
          <Btn kind="secondary">Export alerts</Btn>
          <Btn kind="danger" onClick={onSignOut}>Sign out</Btn>
        </div>
      </div>
    </div>
  );
};

const SettingsCard = ({ title, subtitle, children }) => (
  <div style={{
    background: 'var(--bg-elev)',
    border: '1px solid var(--line)',
    borderRadius: 12,
    padding: '20px 22px',
  }}>
    <div style={{ marginBottom: 14 }}>
      <h3 style={{
        margin: 0,
        fontFamily: 'var(--font-serif)', fontSize: 22, fontWeight: 400,
        letterSpacing: '-0.01em',
      }}>{title}</h3>
      {subtitle && <div style={{ fontSize: 13, color: 'var(--ink-3)', marginTop: 4 }}>{subtitle}</div>}
    </div>
    {children}
  </div>
);

const SettingRow = ({ label, description, right }) => (
  <div style={{
    display: 'flex', alignItems: 'center', justifyContent: 'space-between',
    padding: '12px 0',
    borderTop: '1px solid var(--line)',
  }}>
    <div>
      <div style={{ fontSize: 14, fontWeight: 500 }}>{label}</div>
      {description && <div style={{ fontSize: 12, color: 'var(--ink-3)' }}>{description}</div>}
    </div>
    {right}
  </div>
);

Object.assign(window, { FeedScreen, AlertsScreen, MatchReviewScreen, AddAlertScreen, AdminDashboardScreen, SettingsScreen });
