import React, { useEffect, useMemo, useRef, useState } from 'react';
import { api } from '../api.js';
import { formatRelative } from '../utils/format.js';

const TYPE_LABELS = { domain: 'домен', suffix: 'суффикс', keyword: 'ключевое слово', cidr: 'CIDR', regex: 'regex' };
const PAGE_SIZE = 100;

function RuleSetLookupModal({ tag, url, onClose }) {
  const [state, setState] = useState('idle'); // idle | loading | done | error
  const [error, setError] = useState('');
  const [data, setData] = useState(null); // { entries, stats, cachedAt }
  const [search, setSearch] = useState('');
  const [filterType, setFilterType] = useState('all');
  const [page, setPage] = useState(0);
  const inputRef = useRef(null);

  useEffect(() => {
    setState('loading');
    api.ruleSets.lookup(tag, url)
      .then((res) => { setData(res); setState('done'); })
      .catch((err) => { setError(err.message); setState('error'); });
  }, [tag, url]);

  useEffect(() => {
    if (state === 'done') setTimeout(() => inputRef.current?.focus(), 50);
  }, [state]);

  const filtered = useMemo(() => {
    if (!data?.entries) return [];
    const q = search.trim().toLowerCase();
    return data.entries.filter((e) => {
      if (filterType !== 'all' && e.type !== filterType) return false;
      if (!q) return true;
      return e.value.toLowerCase().includes(q);
    });
  }, [data, search, filterType]);

  const totalPages = Math.ceil(filtered.length / PAGE_SIZE);
  const pageItems = filtered.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE);

  function onSearchChange(v) { setSearch(v); setPage(0); }
  function onTypeChange(v) { setFilterType(v); setPage(0); }

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal lg" style={{ maxWidth: 720, maxHeight: '85vh', display: 'flex', flexDirection: 'column' }} onClick={(e) => e.stopPropagation()}>
        <div className="modal-head">
          <div>
            <h3 style={{ margin: 0 }}>Содержимое: <code style={{ fontSize: 14 }}>{tag}</code></h3>
            <small className="muted">{url}</small>
          </div>
          <button className="btn btn-ghost sm" onClick={onClose}>Закрыть</button>
        </div>

        {state === 'loading' && (
          <div style={{ padding: 32, textAlign: 'center', color: 'var(--text-muted)' }}>
            Скачивание и декомпиляция…<br />
            <small>Может занять 10–30 секунд</small>
          </div>
        )}

        {state === 'error' && (
          <div style={{ padding: 24 }}>
            <div className="conflict-banner danger"><span>✗</span><div>{error}</div></div>
          </div>
        )}

        {state === 'done' && data && (
          <>
            <div style={{ padding: '10px 20px', borderBottom: '1px solid var(--border)' }}>
              <div className="flex" style={{ gap: 8, flexWrap: 'wrap', alignItems: 'center' }}>
                <span className="badge info">всего: {data.stats.total.toLocaleString()}</span>
                {data.stats.domain > 0 && <span className="badge">доменов: {data.stats.domain.toLocaleString()}</span>}
                {data.stats.suffix > 0 && <span className="badge">суффиксов: {data.stats.suffix.toLocaleString()}</span>}
                {data.stats.keyword > 0 && <span className="badge">ключ. слов: {data.stats.keyword.toLocaleString()}</span>}
                {data.stats.cidr > 0 && <span className="badge">CIDR: {data.stats.cidr.toLocaleString()}</span>}
                {data.stats.regex > 0 && <span className="badge">regex: {data.stats.regex.toLocaleString()}</span>}
                <span className="muted" style={{ fontSize: 12, marginLeft: 'auto' }}>
                  кеш: {formatRelative(data.cachedAt)}
                </span>
              </div>
            </div>
            <div style={{ padding: '10px 20px', borderBottom: '1px solid var(--border)', display: 'flex', gap: 8 }}>
              <input
                ref={inputRef}
                className="input"
                style={{ flex: 1 }}
                placeholder="Поиск: youtube, 149.154, .ru…"
                value={search}
                onChange={(e) => onSearchChange(e.target.value)}
              />
              <select className="select" style={{ width: 140 }} value={filterType} onChange={(e) => onTypeChange(e.target.value)}>
                <option value="all">Все типы</option>
                {Object.entries(TYPE_LABELS).map(([k, v]) => (
                  <option key={k} value={k}>{v}</option>
                ))}
              </select>
            </div>
            <div style={{ flex: 1, overflow: 'auto', padding: '0 20px' }}>
              {filtered.length === 0 ? (
                <div className="muted" style={{ padding: '20px 0', textAlign: 'center' }}>Ничего не найдено</div>
              ) : (
                <>
                  <div style={{ fontSize: 12, color: 'var(--text-muted)', padding: '8px 0' }}>
                    Найдено: {filtered.length.toLocaleString()} / {data.stats.total.toLocaleString()}
                    {totalPages > 1 && ` · страница ${page + 1} из ${totalPages}`}
                  </div>
                  <table className="table" style={{ fontSize: 13 }}>
                    <thead>
                      <tr><th style={{ width: 80 }}>Тип</th><th>Значение</th></tr>
                    </thead>
                    <tbody>
                      {pageItems.map((e, i) => (
                        <tr key={i}>
                          <td><span className="badge">{TYPE_LABELS[e.type] || e.type}</span></td>
                          <td className="text-mono" style={{ wordBreak: 'break-all', userSelect: 'all' }}>{e.value}</td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                  {totalPages > 1 && (
                    <div className="flex" style={{ gap: 8, padding: '12px 0', justifyContent: 'center' }}>
                      <button className="btn btn-ghost sm" disabled={page === 0} onClick={() => setPage(0)}>«</button>
                      <button className="btn btn-ghost sm" disabled={page === 0} onClick={() => setPage((p) => p - 1)}>‹</button>
                      <span className="muted" style={{ lineHeight: '28px', fontSize: 13 }}>{page + 1} / {totalPages}</span>
                      <button className="btn btn-ghost sm" disabled={page >= totalPages - 1} onClick={() => setPage((p) => p + 1)}>›</button>
                      <button className="btn btn-ghost sm" disabled={page >= totalPages - 1} onClick={() => setPage(totalPages - 1)}>»</button>
                    </div>
                  )}
                </>
              )}
            </div>
          </>
        )}
      </div>
    </div>
  );
}

// Каталог готовых rule-set источников для sing-box (.srs формат)
// Источники: SagerNet (официальные, используются как встроенные), runetfreedom (RKN-реестр)
const RULE_SET_CATALOG = [
  {
    tag: 'geosite-runet',
    url: 'https://github.com/runetfreedom/russia-blocked-geosite/releases/latest/download/rule-set/ru.srs',
    source: 'runetfreedom',
    category: 'RU',
    description: 'Заблокированные в РФ домены по реестру РКН. Обновляется автоматически из официальных источников.',
    examples: ['rutracker.org', 'youtube.com', 'instagram.com', 'facebook.com', 'twitter.com'],
    use: 'vpn — маршрутизировать заблокированные домены через VPN.',
    builtIn: false,
  },
  {
    tag: 'geoip-ru',
    url: 'https://cdn.jsdelivr.net/gh/SagerNet/sing-geoip@rule-set/geoip-ru.srs',
    source: 'SagerNet/sing-geoip',
    category: 'RU',
    description: 'IP-диапазоны, зарегистрированные в России (RIPE NCC). Покрывает российские хостинги, банки, госсайты.',
    examples: ['77.88.0.0/18 (Яндекс)', '95.173.128.0/19 (МТС)', '213.180.192.0/19 (Яндекс)'],
    use: 'direct — российские сервисы без VPN.',
    builtIn: true,
  },
  {
    tag: 'geosite-category-ru',
    url: 'https://cdn.jsdelivr.net/gh/SagerNet/sing-geosite@rule-set/geosite-category-ru.srs',
    source: 'SagerNet/sing-geosite',
    category: 'RU',
    description: 'Домены российских сервисов: Яндекс, VK, Mail.ru, Сбербанк, банки, госуслуги. Не заблокированные, а просто российские.',
    examples: ['yandex.ru', 'vk.com', 'mail.ru', 'sberbank.ru', 'gosuslugi.ru', 'ozon.ru'],
    use: 'direct — чтобы российские сайты открывались с российским IP (нужно для оплаты и т.п.).',
    builtIn: true,
  },
  {
    tag: 'geosite-google',
    url: 'https://cdn.jsdelivr.net/gh/SagerNet/sing-geosite@rule-set/geosite-google.srs',
    source: 'SagerNet/sing-geosite',
    category: 'Сервисы',
    description: 'Все домены Google: поиск, Gmail, YouTube, Drive, Maps, Google API, reCAPTCHA и пр.',
    examples: ['google.com', 'googleapis.com', 'googlevideo.com', 'gstatic.com', 'ggpht.com'],
    use: 'vpn — если Google заблокирован или нужна стабильная работа сервисов.',
    builtIn: false,
  },
  {
    tag: 'geosite-youtube',
    url: 'https://cdn.jsdelivr.net/gh/SagerNet/sing-geosite@rule-set/geosite-youtube.srs',
    source: 'SagerNet/sing-geosite',
    category: 'Сервисы',
    description: 'Только домены YouTube и связанных CDN. Меньше чем полный Google.',
    examples: ['youtube.com', 'youtu.be', 'ytimg.com', 'googlevideo.com'],
    use: 'vpn — для разблокировки YouTube.',
    builtIn: false,
  },
  {
    tag: 'geosite-telegram',
    url: 'https://cdn.jsdelivr.net/gh/SagerNet/sing-geosite@rule-set/geosite-telegram.srs',
    source: 'SagerNet/sing-geosite',
    category: 'Сервисы',
    description: 'Домены и IP Telegram. Включает CDN, API и голосовые серверы.',
    examples: ['telegram.org', 't.me', 'telegra.ph', '149.154.160.0/20'],
    use: 'vpn — разблокировка в РФ. direct — если хочешь избежать задержек.',
    builtIn: false,
  },
  {
    tag: 'geosite-openai',
    url: 'https://cdn.jsdelivr.net/gh/SagerNet/sing-geosite@rule-set/geosite-openai.srs',
    source: 'SagerNet/sing-geosite',
    category: 'Сервисы',
    description: 'ChatGPT, OpenAI API, Dall-E и другие сервисы OpenAI.',
    examples: ['openai.com', 'chatgpt.com', 'oaistatic.com', 'oaiusercontent.com'],
    use: 'vpn — OpenAI заблокирован в РФ и ряде других стран.',
    builtIn: false,
  },
  {
    tag: 'geosite-apple',
    url: 'https://cdn.jsdelivr.net/gh/SagerNet/sing-geosite@rule-set/geosite-apple.srs',
    source: 'SagerNet/sing-geosite',
    category: 'Сервисы',
    description: 'App Store, iCloud, Apple CDN, push-уведомления (APNs), iMessage.',
    examples: ['apple.com', 'icloud.com', 'mzstatic.com', 'apple-cloudkit.com'],
    use: 'direct — Apple обычно работает без VPN. vpn — если нужен другой регион App Store.',
    builtIn: false,
  },
  {
    tag: 'geosite-github',
    url: 'https://cdn.jsdelivr.net/gh/SagerNet/sing-geosite@rule-set/geosite-github.srs',
    source: 'SagerNet/sing-geosite',
    category: 'Разработка',
    description: 'GitHub, GitHub Actions, GitHub Pages, raw.githubusercontent.com.',
    examples: ['github.com', 'githubusercontent.com', 'github.io', 'githubassets.com'],
    use: 'vpn — если GitHub замедлен или заблокирован.',
    builtIn: false,
  },
  {
    tag: 'geosite-category-ads-all',
    url: 'https://cdn.jsdelivr.net/gh/SagerNet/sing-geosite@rule-set/geosite-category-ads-all.srs',
    source: 'SagerNet/sing-geosite',
    category: 'Блокировка',
    description: 'Рекламные сети, трекеры, аналитика. Тысячи доменов.',
    examples: ['doubleclick.net', 'googlesyndication.com', 'amazon-adsystem.com'],
    use: 'block — блокировка рекламы и трекеров на уровне DNS.',
    builtIn: false,
  },
];

function SubscriptionCard({ state, subscriptionUrl, setSubscriptionUrl, busy, onFetch, onForget, pushToast }) {
  const [editing, setEditing] = useState(!state?.hasSubscription);

  useEffect(() => { if (!state?.hasSubscription) setEditing(true); }, [state?.hasSubscription]);

  const masked = state?.hasSubscription && !editing;

  return (
    <div className="card">
      <div className="card-header">
        <h2>Подписка</h2>
        {state?.hasSubscription && (
          <span className="badge success">● активна</span>
        )}
      </div>

      {masked ? (
        <div className="kv-list">
          <div className="row">
            <span className="key">URL</span>
            <span className="val text-mono">{state.subscriptionHost}</span>
          </div>
          <div className="row">
            <span className="key">Серверов</span>
            <span className="val">{state.servers?.length || 0}</span>
          </div>
          <div className="row">
            <span className="key">Загружено</span>
            <span className="val">{state.fetchedAt ? formatRelative(state.fetchedAt) : '—'}</span>
          </div>
        </div>
      ) : (
        <div className="field">
          <span className="field-label">Subscription URL</span>
          <div className="subscription-input">
            <input
              className="input"
              value={subscriptionUrl}
              onChange={(e) => setSubscriptionUrl(e.target.value)}
              placeholder="https://provider.example/sub/..."
            />
          </div>
        </div>
      )}

      <div className="btn-group" style={{ marginTop: 16 }}>
        {masked ? (
          <>
            <button className="btn btn-secondary" onClick={() => setEditing(true)} disabled={busy}>Изменить URL</button>
            <button className="btn btn-secondary" onClick={onFetch} disabled={busy}>↻ Обновить серверы</button>
            <button className="btn btn-danger" onClick={onForget} disabled={busy}>Удалить подписку</button>
          </>
        ) : (
          <>
            <button
              className="btn btn-primary"
              onClick={async () => { await onFetch(); setEditing(false); }}
              disabled={busy || !subscriptionUrl}
            >
              {busy ? 'Загрузка…' : 'Загрузить серверы'}
            </button>
            {state?.hasSubscription && (
              <button className="btn btn-ghost" onClick={() => setEditing(false)}>Отмена</button>
            )}
          </>
        )}
      </div>
    </div>
  );
}

function ConfigCard({ state, busy, onShowConfig, onClearConfig, pushToast }) {
  const [validation, setValidation] = useState(null);
  const [validating, setValidating] = useState(false);

  async function validate() {
    setValidating(true);
    try {
      const data = await api.configValidate();
      setValidation(data);
      pushToast({
        kind: data.valid ? 'success' : 'danger',
        title: data.valid ? 'Config валиден' : 'Config невалиден',
        message: data.error || data.note,
      });
    } catch (err) {
      pushToast({ kind: 'danger', title: 'Ошибка проверки', message: err.message });
    } finally {
      setValidating(false);
    }
  }

  return (
    <div className="card">
      <div className="card-header">
        <h2>sing-box config</h2>
        {validation && (
          <span className={`badge ${validation.valid ? 'success' : 'danger'}`}>
            {validation.valid ? '✓ валиден' : '✗ ошибка'}
          </span>
        )}
      </div>
      <div className="kv-list">
        <div className="row"><span className="key">Файл</span><span className="val">{state?.configExists ? 'есть' : 'нет'}</span></div>
        <div className="row"><span className="key">Применено</span><span className="val">{state?.appliedAt ? formatRelative(state.appliedAt) : '—'}</span></div>
      </div>
      <div className="btn-group" style={{ marginTop: 16 }}>
        <button className="btn btn-secondary" disabled={!state?.configExists} onClick={onShowConfig}>Показать config</button>
        <button className="btn btn-secondary" disabled={validating || !state?.configExists} onClick={validate}>
          {validating ? 'Проверяем…' : '✓ Валидировать'}
        </button>
        <button className="btn btn-danger" disabled={busy || !state?.configExists} onClick={onClearConfig}>
          Сбросить config
        </button>
      </div>
      {validation && !validation.valid && validation.error && (
        <div className="conflict-banner danger" style={{ marginTop: 12 }}>
          <span>✗</span><div>{validation.error}</div>
        </div>
      )}
    </div>
  );
}

const CATALOG_CATEGORIES = ['Все', ...Array.from(new Set(RULE_SET_CATALOG.map((r) => r.category)))];

function CatalogEntry({ entry, added, busy, onAdd, onLookup }) {
  const [open, setOpen] = useState(false);
  return (
    <div style={{ border: '1px solid var(--border)', borderRadius: 8, padding: '10px 14px', marginBottom: 8 }}>
      <div className="flex" style={{ alignItems: 'center', gap: 8 }}>
        <div style={{ flex: 1 }}>
          <div className="flex" style={{ alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
            <strong className="text-mono" style={{ fontSize: 13 }}>{entry.tag}</strong>
            <span className="badge info" style={{ fontSize: 11 }}>{entry.category}</span>
            <span className="muted" style={{ fontSize: 12 }}>{entry.source}</span>
            {entry.builtIn && (
              <span className="badge success" style={{ fontSize: 11 }} title="Загружается автоматически при включённом RU direct">встроен</span>
            )}
          </div>
          <div style={{ fontSize: 13, marginTop: 2, color: 'var(--text)' }}>{entry.description}</div>
        </div>
        <div className="flex" style={{ gap: 6, flexShrink: 0 }}>
          <button
            className="btn btn-ghost sm"
            onClick={() => setOpen((o) => !o)}
            title="Примеры и подсказка"
          >
            {open ? '▲' : '▼'}
          </button>
          <button
            className="btn btn-ghost sm"
            onClick={() => onLookup(entry)}
            title="Просмотреть содержимое и искать внутри"
          >
            🔍
          </button>
          <button
            className="btn btn-secondary sm"
            disabled={busy || added}
            onClick={() => onAdd(entry)}
          >
            {added ? '✓ добавлен' : '+ Добавить'}
          </button>
        </div>
      </div>
      {open && (
        <div style={{ marginTop: 10, paddingTop: 10, borderTop: '1px solid var(--border)' }}>
          <div style={{ fontSize: 12, marginBottom: 6 }}>
            <span className="muted">Примеры содержимого: </span>
            {entry.examples.map((ex, i) => (
              <span key={i}>
                <code style={{ background: 'var(--bg-muted)', borderRadius: 3, padding: '1px 5px', fontSize: 11 }}>{ex}</code>
                {i < entry.examples.length - 1 ? ' ' : ''}
              </span>
            ))}
          </div>
          <div style={{ fontSize: 12 }}>
            <span className="muted">Рекомендуемый outbound: </span>
            <span>{entry.use}</span>
          </div>
        </div>
      )}
    </div>
  );
}

function SagerNetSearchCard({ ruleSets, onAdd, busy }) {
  const [open, setOpen] = useState(false);
  const [status, setStatus] = useState('idle'); // idle | loading | done | error
  const [catalog, setCatalog] = useState(null); // { geosite, geoip, cachedAt }
  const [error, setError] = useState('');
  const [query, setQuery] = useState('');
  const [repoFilter, setRepoFilter] = useState('all'); // all | geosite | geoip

  function load() {
    if (status !== 'idle') return;
    setStatus('loading');
    api.ruleSets.sagernetCatalog()
      .then((d) => { setCatalog(d); setStatus('done'); })
      .catch((err) => { setError(err.message); setStatus('error'); });
  }

  function toggle() {
    if (!open && status === 'idle') load();
    setOpen((o) => !o);
  }

  const results = useMemo(() => {
    if (!catalog) return [];
    const q = query.trim().toLowerCase();
    const toItem = (repo) => (name) => ({ name, repo, url: `https://cdn.jsdelivr.net/gh/SagerNet/sing-${repo}@rule-set/${name}.srs` });
    const gs = repoFilter !== 'geoip' ? (catalog.geosite || []).map(toItem('geosite')) : [];
    const gi = repoFilter !== 'geosite' ? (catalog.geoip || []).map(toItem('geoip')) : [];
    const all = [...gs, ...gi];
    if (!q) return all;
    return all.filter((item) => item.name.includes(q));
  }, [catalog, query, repoFilter]);

  const addedTags = new Set(ruleSets.map((rs) => rs.tag));

  return (
    <div className="card">
      <div className="card-header" style={{ cursor: 'pointer' }} onClick={toggle}>
        <h2>Поиск в каталоге SagerNet</h2>
        <div className="flex" style={{ gap: 8, alignItems: 'center' }}>
          {status === 'done' && catalog && (
            <span className="badge info" style={{ fontSize: 11 }}>
              {(catalog.geosite?.length || 0) + (catalog.geoip?.length || 0)} rule-sets
            </span>
          )}
          <span className="muted" style={{ fontSize: 13 }}>{open ? '▲' : '▼'}</span>
        </div>
      </div>

      {open && (
        <>
          {status === 'loading' && (
            <div style={{ padding: '20px 0', textAlign: 'center', color: 'var(--text-muted)' }}>
              Загрузка списка из GitHub…
            </div>
          )}
          {status === 'error' && (
            <div className="conflict-banner danger" style={{ marginTop: 8 }}>
              <span>✗</span><div>{error}</div>
            </div>
          )}
          {status === 'done' && (
            <>
              <small className="muted" style={{ display: 'block', marginBottom: 12 }}>
                Полный список rule-sets из репозиториев <strong>SagerNet/sing-geosite</strong> и <strong>SagerNet/sing-geoip</strong>.
                Ищите по имени: <code>steam</code>, <code>gaming</code>, <code>netflix</code>, <code>apple</code> и т.д.
                Кеш обновляется раз в 24 ч.
              </small>
              {catalog.fallback && (
                <div className="conflict-banner warning" style={{ marginBottom: 12 }}>
                  <span>!</span><div>{catalog.warning || 'Показан встроенный fallback-каталог.'}</div>
                </div>
              )}
              <div className="flex" style={{ gap: 8, marginBottom: 12, flexWrap: 'wrap' }}>
                <input
                  className="input"
                  style={{ flex: 1, minWidth: 180 }}
                  placeholder="steam, gaming, netflix, cloudflare…"
                  value={query}
                  onChange={(e) => setQuery(e.target.value)}
                  autoFocus
                />
                <select className="select" style={{ width: 130 }} value={repoFilter} onChange={(e) => setRepoFilter(e.target.value)}>
                  <option value="all">geosite + geoip</option>
                  <option value="geosite">только geosite</option>
                  <option value="geoip">только geoip</option>
                </select>
              </div>

              {query.trim() === '' ? (
                <div className="muted" style={{ fontSize: 13, padding: '8px 0' }}>
                  Введите запрос — покажем совпадения ({(catalog.geosite?.length || 0) + (catalog.geoip?.length || 0)} доступно)
                </div>
              ) : results.length === 0 ? (
                <div className="muted" style={{ fontSize: 13, padding: '8px 0' }}>Ничего не найдено</div>
              ) : (
                <table className="table" style={{ fontSize: 13 }}>
                  <thead>
                    <tr>
                      <th style={{ width: 60 }}>Тип</th>
                      <th>Тег</th>
                      <th style={{ width: 120 }}></th>
                    </tr>
                  </thead>
                  <tbody>
                    {results.slice(0, 100).map((item) => (
                      <tr key={item.name}>
                        <td><span className={`badge ${item.repo === 'geosite' ? 'info' : ''}`} style={{ fontSize: 11 }}>{item.repo}</span></td>
                        <td className="text-mono">{item.name}</td>
                        <td style={{ textAlign: 'right' }}>
                          {addedTags.has(item.name) ? (
                            <span className="badge success" style={{ fontSize: 11 }}>✓ добавлен</span>
                          ) : (
                            <button
                              className="btn btn-secondary sm"
                              disabled={busy}
                              onClick={() => onAdd({ tag: item.name, url: item.url })}
                            >
                              + Добавить
                            </button>
                          )}
                        </td>
                      </tr>
                    ))}
                  </tbody>
                </table>
              )}
              {results.length > 100 && (
                <div className="muted" style={{ fontSize: 12, marginTop: 8 }}>
                  Показано 100 из {results.length} — уточните запрос
                </div>
              )}
              <div className="muted" style={{ fontSize: 11, marginTop: 12 }}>
                кеш: {catalog.cachedAt ? formatRelative(catalog.cachedAt) : '—'}
              </div>
            </>
          )}
        </>
      )}
    </div>
  );
}

function RuleSetsCard({ pushToast }) {
  const [ruleSets, setRuleSets] = useState([]);
  const [newTag, setNewTag] = useState('');
  const [newUrl, setNewUrl] = useState('');
  const [busy, setBusy] = useState(false);
  const [search, setSearch] = useState('');
  const [category, setCategory] = useState('Все');
  const [lookup, setLookup] = useState(null); // { tag, url }

  useEffect(() => {
    api.ruleSets.get().then((d) => setRuleSets(d.ruleSets || [])).catch(() => {});
  }, []);

  async function save(next) {
    setBusy(true);
    try {
      const data = await api.ruleSets.save(next);
      setRuleSets(data.ruleSets || []);
      pushToast({ kind: 'success', title: 'Rule-sets сохранены' });
    } catch (err) {
      pushToast({ kind: 'danger', title: 'Ошибка', message: err.message });
    } finally {
      setBusy(false);
    }
  }

  function addNew() {
    const tag = newTag.trim();
    const url = newUrl.trim();
    if (!tag || !url) return;
    if (!/^[a-z0-9][a-z0-9_.@!-]*$/i.test(tag)) {
      pushToast({ kind: 'danger', title: 'Невалидный тег', message: 'Буквы, цифры и символы - _ . @ !' });
      return;
    }
    if (ruleSets.some((rs) => rs.tag === tag)) {
      pushToast({ kind: 'danger', title: 'Тег уже существует' });
      return;
    }
    const next = [...ruleSets, { tag, url }];
    setNewTag('');
    setNewUrl('');
    save(next);
  }

  function remove(tag) {
    save(ruleSets.filter((rs) => rs.tag !== tag));
  }

  function addFromCatalog(entry) {
    if (ruleSets.some((rs) => rs.tag === entry.tag)) {
      pushToast({ kind: 'info', title: `${entry.tag} уже добавлен` });
      return;
    }
    save([...ruleSets, { tag: entry.tag, url: entry.url }]);
  }

  const q = search.trim().toLowerCase();
  const filtered = RULE_SET_CATALOG.filter((entry) => {
    if (category !== 'Все' && entry.category !== category) return false;
    if (!q) return true;
    return (
      entry.tag.includes(q) ||
      entry.description.toLowerCase().includes(q) ||
      entry.source.toLowerCase().includes(q) ||
      entry.examples.some((ex) => ex.toLowerCase().includes(q))
    );
  });

  return (
    <>
    <div className="card">
      <div className="card-header">
        <h2>Источники (rule-sets)</h2>
      </div>
      <small className="muted" style={{ display: 'block', marginBottom: 16 }}>
        Geo-базы в формате <strong>.srs</strong> (sing-box). Sing-box скачает их автоматически при применении.
        <strong> .dat файлы (v2ray) не поддерживаются</strong>.
      </small>

      {ruleSets.length > 0 && (
        <>
          <div className="field-label" style={{ marginBottom: 6 }}>Подключённые</div>
          <table className="table" style={{ marginBottom: 20 }}>
            <thead>
              <tr>
                <th>Тег</th>
                <th>URL</th>
                <th></th>
              </tr>
            </thead>
            <tbody>
              {ruleSets.map((rs) => (
                <tr key={rs.tag}>
                  <td className="text-mono" style={{ whiteSpace: 'nowrap' }}>{rs.tag}</td>
                  <td className="muted" style={{ fontSize: 12, wordBreak: 'break-all' }}>{rs.url}</td>
                  <td style={{ textAlign: 'right', whiteSpace: 'nowrap' }}>
                    <button className="btn btn-ghost sm" style={{ marginRight: 4 }} onClick={() => setLookup(rs)} title="Просмотреть содержимое">🔍</button>
                    <button className="btn btn-ghost sm" disabled={busy} onClick={() => remove(rs.tag)}>×</button>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </>
      )}

      <div className="field-label" style={{ marginBottom: 8 }}>Каталог</div>
      <div className="flex" style={{ gap: 8, marginBottom: 12, flexWrap: 'wrap' }}>
        <input
          className="input"
          style={{ flex: 1, minWidth: 180 }}
          placeholder="Поиск: telegram, реклама, youtube…"
          value={search}
          onChange={(e) => setSearch(e.target.value)}
        />
        <select className="select" style={{ width: 140 }} value={category} onChange={(e) => setCategory(e.target.value)}>
          {CATALOG_CATEGORIES.map((c) => <option key={c}>{c}</option>)}
        </select>
      </div>

      {filtered.length === 0 && (
        <div className="muted" style={{ fontSize: 13, marginBottom: 12 }}>Ничего не найдено</div>
      )}
      {filtered.map((entry) => (
        <CatalogEntry
          key={entry.tag}
          entry={entry}
          added={ruleSets.some((rs) => rs.tag === entry.tag)}
          busy={busy}
          onAdd={addFromCatalog}
          onLookup={(e) => setLookup(e)}
        />
      ))}

      <div className="field" style={{ marginTop: 16 }}>
        <span className="field-label">Добавить свой .srs</span>
        <div className="flex" style={{ gap: 8, flexWrap: 'wrap' }}>
          <input
            className="input"
            style={{ width: 200 }}
            placeholder="тег (напр. geosite-custom)"
            value={newTag}
            onChange={(e) => setNewTag(e.target.value)}
          />
          <input
            className="input"
            style={{ flex: 1, minWidth: 200 }}
            placeholder="https://…/rule-set.srs"
            value={newUrl}
            onChange={(e) => setNewUrl(e.target.value)}
          />
          <button className="btn btn-primary" disabled={busy || !newTag || !newUrl} onClick={addNew}>
            Добавить
          </button>
        </div>
      </div>
    </div>
    <SagerNetSearchCard ruleSets={ruleSets} onAdd={addFromCatalog} busy={busy} />
    {lookup && (
      <RuleSetLookupModal
        tag={lookup.tag}
        url={lookup.url}
        onClose={() => setLookup(null)}
      />
    )}
    </>
  );
}

function PortsCard({ state }) {
  const isClient = state?.mode === 'client';
  return (
    <div className="card">
      <div className="card-header"><h2>{isClient ? 'Локальные порты' : 'Порты и маршруты'}</h2></div>
      <div className="kv-list">
        <div className="row"><span className="key">UI</span><span className="val text-mono">:{state?.port || 3456}</span></div>
        <div className="row"><span className="key">HTTP/SOCKS proxy</span><span className="val text-mono">{isClient ? '127.0.0.1' : state?.proxyBindIp || '0.0.0.0'}:{state?.proxyPort || 8080}</span></div>
        {!isClient && <div className="row"><span className="key">TProxy</span><span className="val text-mono">:{state?.tproxyPort || 7895}</span></div>}
        <div className="row"><span className="key">RU direct (geoip-ru)</span><span className="val">{state?.routingRuDirect ? 'включено' : 'выключено'}</span></div>
      </div>
    </div>
  );
}

export function SettingsPage({
  state, subscriptionUrl, setSubscriptionUrl, busy,
  onFetchSubscription, onForgetSubscription, onShowConfig, onClearConfig, pushToast,
}) {
  return (
    <div className="section-stack">
      <SubscriptionCard
        state={state}
        subscriptionUrl={subscriptionUrl}
        setSubscriptionUrl={setSubscriptionUrl}
        busy={busy}
        onFetch={onFetchSubscription}
        onForget={onForgetSubscription}
        pushToast={pushToast}
      />
      <ConfigCard
        state={state}
        busy={busy}
        onShowConfig={onShowConfig}
        onClearConfig={onClearConfig}
        pushToast={pushToast}
      />
      <RuleSetsCard pushToast={pushToast} />
      <PortsCard state={state} />
    </div>
  );
}
