/* User-facing clusterer — redesigned UX (v2) */
const { useState, useMemo, useEffect, useRef, useCallback } = React;

/* ---------- Fingerprint + free-plan usage ---------- */
function getFingerprint() {
  try {
    let fp = localStorage.getItem('sv-fp');
    if (fp) return fp;
    const sig = [
      navigator.userAgent, navigator.language, screen.width + 'x' + screen.height,
      Intl.DateTimeFormat().resolvedOptions().timeZone,
      navigator.hardwareConcurrency || 0,
    ].join('|');
    let hash = 0;
    for (const c of sig) hash = (hash * 31 + c.charCodeAt(0)) | 0;
    fp = 'fp_' + Math.abs(hash).toString(36);
    localStorage.setItem('sv-fp', fp);
    return fp;
  } catch { return 'fp_anon'; }
}

const FREE_LIMITS = { rows: 50 };

function useFreeUsage() {
  const fp = getFingerprint();
  return { fp, rowLimit: FREE_LIMITS.rows };
}

/* ---------- Intent + frequency mocks ---------- */
const INTENT_RULES = [
  { kind: 'transact', label: 'транзакц.', color: '#F5821F', words: ['купить','заказать','оформить','оплатить','доставка'] },
  { kind: 'commerce', label: 'коммерч.', color: '#5B8DEF', words: ['цена','стоимость','прайс','скидка','каталог','магазин','недорого'] },
  { kind: 'inform',   label: 'информ.',   color: '#3FBF87', words: ['как','что','зачем','отзывы','сравнение','рейтинг','лучший','лучшие','выбрать'] },
  { kind: 'navig',    label: 'навиг.',    color: '#9B7CF6', words: ['xiaomi','karcher','dyson','samsung','lg','roborock','irobot'] },
];

function detectIntent(phrases) {
  const counts = { transact: 0, commerce: 0, inform: 0, navig: 0, none: 0 };
  for (const p of phrases) {
    const low = p.toLowerCase();
    let found = false;
    for (const r of INTENT_RULES) {
      if (r.words.some(w => low.includes(w))) { counts[r.kind]++; found = true; break; }
    }
    if (!found) counts.none++;
  }
  const dominant = Object.entries(counts).sort((a,b)=>b[1]-a[1])[0][0];
  return INTENT_RULES.find(r => r.kind === dominant) || { kind: 'mixed', label: 'смешан.', color: '#a8a4a0' };
}

function estimateFreq(name) {
  // deterministic pseudo-random based on phrase
  let h = 0; for (const c of name) h = (h * 137 + c.charCodeAt(0)) | 0;
  const base = (Math.abs(h) % 9000) + 50;
  return base;
}

function fmtFreq(n) {
  if (n >= 1000) return (n/1000).toFixed(1).replace('.0','') + 'K';
  return String(n);
}

/* ---------- App ---------- */
function ClusterApp() {
  const tweaks = useAccentTheme();
  const usage = useFreeUsage();

  const [phrases, setPhrases] = useState(sampleData.phrases);
  const [ignoreWords, setIgnoreWords] = useState(sampleData.ignore);
  const [method, setMethod] = useState('soft'); // soft | hard | serp
  const [strength, setStrength] = useState(4);  // 3..7 (industry standard)
  const [minSize, setMinSize] = useState(1);
  const [advancedOpen, setAdvancedOpen] = useState(false);
  const [useEmbeddings, setUseEmbeddings] = useState(false);
  const [preset, setPreset] = useState('ecommerce');

  // Pro-фича для платного тарифа и админа. Без реальной auth админ
  // помечается флагом в localStorage (`sv-admin=1`).
  const isAdmin = useMemo(() => {
    try { return localStorage.getItem('sv-admin') === '1'; } catch { return false; }
  }, []);

  const [running, setRunning] = useState(false);
  const [results, setResults] = useState(null);
  const [meta, setMeta] = useState(null);

  const [view, setView] = useState('cards'); // cards | table | graph
  const [search, setSearch] = useState('');
  const [intentFilter, setIntentFilter] = useState('all');
  const [sortOrder, setSortOrder] = useState('countDesc');
  const [openGroups, setOpenGroups] = useState(new Set());
  const [tweakOpen, setTweakOpen] = useState(false);

  const [paywall, setPaywall] = useState(null); // null | string (feature name)

  /* Tweaks panel protocol */
  useEffect(() => {
    function onMsg(e) {
      if (e.data?.type === '__activate_edit_mode') setTweakOpen(true);
      if (e.data?.type === '__deactivate_edit_mode') setTweakOpen(false);
    }
    window.addEventListener('message', onMsg);
    window.parent.postMessage({type: '__edit_mode_available'}, '*');
    return () => window.removeEventListener('message', onMsg);
  }, []);

  const lines = useMemo(() => phrases.split('\n').filter(l => l.trim()), [phrases]);
  const overLimit = lines.length > usage.rowLimit;

  const runCluster = async () => {
    if (method === 'serp') { setPaywall('SERP-кластеризация'); return; }
    if (overLimit) return;
    setRunning(true);
    const t0 = performance.now();
    try {
      const ignore = ignoreWords.split(',').map(s => s.trim()).filter(Boolean);
      const sens = (8 - strength) / 6;
      const out = await clusterPhrases(lines, { ignoreWords: ignore, minSize, sensitivity: sens, useEmbeddings });
      const enriched = out.map(g => ({
        ...g,
        intent: detectIntent(g.phrases),
        freq: estimateFreq(g.name) * Math.max(1, Math.floor(g.size * 0.5 + 1)),
        url: '/' + g.name.replace(/\s+/g, '-').replace(/[^a-zа-я0-9-]/gi,'').slice(0, 30),
      }));
      const total = lines.length;
      const unique = new Set(lines.map(l => l.trim().toLowerCase())).size;
      setResults(enriched);
      setMeta({
        total, unique, groups: enriched.length,
        ms: Math.round(performance.now() - t0),
        method, strength,
      });
      setOpenGroups(new Set(enriched.slice(0, 2).map(g => g.name)));
    } catch (err) {
      console.error('cluster failed', err);
      setMeta({
        total: lines.length, unique: 0, groups: 0,
        ms: Math.round(performance.now() - t0),
        method, strength,
        error: String(err && err.message || err),
      });
      setResults([]);
    } finally {
      setRunning(false);
    }
  };

  const filteredResults = useMemo(() => {
    if (!results) return null;
    let r = results.filter(g =>
      (intentFilter === 'all' || g.intent.kind === intentFilter) &&
      (!search ||
        g.name.toLowerCase().includes(search.toLowerCase()) ||
        g.phrases.some(p => p.toLowerCase().includes(search.toLowerCase())))
    );
    const ord = {
      countDesc: (a,b) => b.size - a.size,
      countAsc: (a,b) => a.size - b.size,
      freqDesc: (a,b) => b.freq - a.freq,
      nameAsc: (a,b) => a.name.localeCompare(b.name, 'ru'),
    };
    return [...r].sort(ord[sortOrder]);
  }, [results, search, intentFilter, sortOrder]);

  const intentCounts = useMemo(() => {
    if (!results) return {};
    const c = { all: results.length };
    results.forEach(g => { c[g.intent.kind] = (c[g.intent.kind]||0)+1; });
    return c;
  }, [results]);

  const toggleGroup = (name) => {
    const s = new Set(openGroups);
    s.has(name) ? s.delete(name) : s.add(name);
    setOpenGroups(s);
  };

  const downloadCsv = () => {
    if (!results) return;
    const rows = [['cluster','main_phrase','intent','est_freq','phrase']];
    results.forEach(g => g.phrases.forEach(p => rows.push([g.name, g.phrases[0], g.intent.label, g.freq, p])));
    const csv = rows.map(r => r.map(x => `"${String(x).replace(/"/g,'""')}"`).join(',')).join('\n');
    const blob = new Blob([csv], {type: 'text/csv'});
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob); a.download = 'clusters.csv'; a.click();
  };

  const handleRename = (oldName, newName) => {
    setResults(results.map(g => g.name === oldName ? {...g, name: newName} : g));
  };
  const handleMovePhrase = (fromName, phrase, toName) => {
    setResults(results.map(g => {
      if (g.name === fromName) return {...g, phrases: g.phrases.filter(p => p !== phrase), size: g.phrases.length - 1};
      if (g.name === toName)   return {...g, phrases: [...g.phrases, phrase], size: g.phrases.length + 1};
      return g;
    }).filter(g => g.size > 0));
  };
  const handleDeleteGroup = (name) => setResults(results.filter(g => g.name !== name));
  const handleMergeGroups = (a, b) => {
    setResults(results
      .map(g => g.name === a ? {...g, phrases: [...g.phrases, ...results.find(x => x.name === b).phrases], size: g.phrases.length + results.find(x => x.name === b).phrases.length} : g)
      .filter(g => g.name !== b));
  };

  /* shortcut */
  useEffect(() => {
    const h = (e) => {
      if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
        e.preventDefault(); runCluster();
      }
    };
    window.addEventListener('keydown', h);
    return () => window.removeEventListener('keydown', h);
  });

  const onFileDrop = async (file) => {
    const text = await file.text();
    setPhrases(text.trim());
  };

  return (
    <div className="app">
      <SavovHeader section="user" planBadge={<PlanBadge />} />
      <main className="shell" style={{padding: '32px 32px 64px', flex: 1}}>

        {/* Hero */}
        <div className="row" style={{justifyContent: 'space-between', marginBottom: 28, flexWrap: 'wrap', gap: 24}}>
          <div style={{maxWidth: 760}}>
            <div className="eyebrow" style={{color: 'var(--accent)'}}>· семантика для SEO-команд</div>
            <h1 className="display" style={{fontSize: 56, margin: '12px 0 12px'}}>
              Кластеризация запросов<br/>
              <span style={{color: 'var(--text-dim)'}}>в один клик</span>
            </h1>
            <p className="muted" style={{maxWidth: 560, fontSize: 16, margin: 0}}>
              Soft, Hard и SERP-методы. Лемматизация под русский, intent-классификатор,
              оценка частотности и подсказка посадочной для каждого кластера.
            </p>
          </div>
          <div className="col" style={{minWidth: 280, gap: 8}}>
            <Stat label="Фраз обработано" value="48.2M" />
            <Stat label="Средняя точность" value="94%" />
            <Stat label="Время на 10K фраз" value="6.4с" />
          </div>
        </div>

        {/* Method picker */}
        <MethodPicker method={method} onChange={setMethod} onProClick={() => setPaywall('SERP-кластеризация')} />

        {/* Workspace */}
        <div className="card card-section" style={{marginBottom: 16, padding: 0, overflow: 'hidden'}}>
          <UsageStrip lines={lines.length} rowLimit={usage.rowLimit} />
          <div style={{padding: 28}}>
            <PhraseInput
              phrases={phrases} setPhrases={setPhrases}
              rowLimit={usage.rowLimit} onFileDrop={onFileDrop}
              preset={preset} setPreset={setPreset}
            />
            <SettingsStrip
              method={method} strength={strength} setStrength={setStrength}
              minSize={minSize} setMinSize={setMinSize}
              ignoreWords={ignoreWords} setIgnoreWords={setIgnoreWords}
              advancedOpen={advancedOpen} setAdvancedOpen={setAdvancedOpen}
              useEmbeddings={useEmbeddings} setUseEmbeddings={setUseEmbeddings}
              isAdmin={isAdmin}
              onProClick={(name) => setPaywall(name)}
            />
            <div className="row" style={{gap: 12, marginTop: 22, alignItems: 'stretch'}}>
              <button
                className="btn btn-pri btn-lg"
                style={{flex: 1, fontSize: 17}}
                disabled={running || overLimit || lines.length === 0}
                onClick={runCluster}>
                {running ? 'Группирую…'
                  : overLimit ? `Превышен лимит — ${lines.length}/${usage.rowLimit} строк`
                  : `Сгруппировать  ·  ${lines.length} фраз`}
                <span className="kbd" style={{marginLeft: 8, opacity:.7, background: 'rgba(0,0,0,0.15)', borderColor: 'transparent', color: 'var(--accent-text)'}}>⌘ ⏎</span>
              </button>
              <button className="btn btn-soft btn-lg" disabled={!results} onClick={downloadCsv}>
                ↓ CSV
              </button>
              <button className="btn btn-ghost btn-lg" onClick={() => setPaywall('XLSX-экспорт')}>
                ↓ XLSX <ProDot />
              </button>
            </div>
            {meta && (
              <div className="row dim" style={{fontSize: 12, gap: 14, flexWrap: 'wrap', marginTop: 14, justifyContent: 'center'}}>
                <span>Всего: <b style={{color:'var(--text)'}}>{meta.total}</b></span>
                <span>·</span>
                <span>Уникальных: <b style={{color:'var(--text)'}}>{meta.unique}</b></span>
                <span>·</span>
                <span>Групп: <b style={{color:'var(--text)'}}>{meta.groups}</b></span>
                <span>·</span>
                <span>Метод: <b className="mono" style={{color:'var(--text)'}}>{meta.method}</b></span>
                <span>·</span>
                <span>{meta.ms} мс</span>
              </div>
            )}
          </div>
        </div>

        {/* Results */}
        {filteredResults ? (
          <ResultsView
            results={filteredResults}
            allResults={results}
            view={view} setView={setView}
            search={search} setSearch={setSearch}
            intentFilter={intentFilter} setIntentFilter={setIntentFilter}
            intentCounts={intentCounts}
            sortOrder={sortOrder} setSortOrder={setSortOrder}
            openGroups={openGroups} toggleGroup={toggleGroup}
            onRename={handleRename}
            onMovePhrase={handleMovePhrase}
            onDeleteGroup={handleDeleteGroup}
            onMergeGroups={handleMergeGroups}
            onProClick={(name) => setPaywall(name)}
          />
        ) : (
          <EmptyState />
        )}

        {/* API teaser */}
        <ApiPromo onProClick={(name) => setPaywall(name)} />

        {/* Features */}
        <FeatureGrid />
      </main>
      <SavovFooter />

      {tweakOpen && <TweakPanel tweaks={tweaks} onClose={() => {
        setTweakOpen(false);
        window.parent.postMessage({type: '__edit_mode_dismissed'}, '*');
      }} />}

      {paywall && <PaywallModal feature={paywall} onClose={() => setPaywall(null)} />}
    </div>
  );
}

/* ============ UI atoms ============ */

function PlanBadge() {
  return (
    <div className="row" style={{gap: 8, fontSize: 12}}>
      <span className="chip" style={{padding: '6px 12px'}}>
        <span style={{color: 'var(--text-dim)', marginRight: 4}}>план:</span>
        <b>Free</b>
      </span>
    </div>
  );
}

function Stat({ label, value }) {
  return (
    <div className="row" style={{justifyContent:'space-between', borderBottom:'1px solid var(--border)', padding:'8px 0'}}>
      <span className="muted" style={{fontSize: 12}}>{label}</span>
      <span className="display" style={{fontSize: 22}}>{value}</span>
    </div>
  );
}

function ProDot() {
  return <span style={{
    display:'inline-flex', alignItems:'center', justifyContent:'center',
    width: 22, height: 14, fontSize: 9, fontWeight: 700,
    borderRadius: 4, background: 'var(--accent-soft)', color: 'var(--accent)',
    letterSpacing: '0.06em',
  }}>PRO</span>;
}

function MethodPicker({ method, onChange, onProClick }) {
  const methods = [
    { id: 'soft', name: 'Soft', desc: 'По леммам и стемам. Широкие группы. Подходит для информационных запросов.', badge: 'free' },
    { id: 'hard', name: 'Hard', desc: 'Жёсткое совпадение базиса. Точные группы. Подходит для коммерческих ниш.', badge: 'free' },
    { id: 'serp', name: 'SERP', desc: 'По пересечению топ-10 Яндекса. Эталонный метод для SEO-агентств.', badge: 'pro' },
  ];
  return (
    <div className="row" style={{gap: 10, marginBottom: 16}}>
      {methods.map(m => (
        <button
          key={m.id}
          className="card"
          onClick={() => m.badge === 'pro' ? onProClick() : onChange(m.id)}
          style={{
            flex: 1, padding: 18, textAlign: 'left', cursor: 'pointer',
            background: method === m.id ? 'var(--accent-soft)' : 'var(--bg-card)',
            borderColor: method === m.id ? 'var(--accent)' : 'var(--border)',
            borderRadius: 16,
            transition: '120ms', font: 'inherit', color: 'inherit',
            opacity: m.badge === 'pro' && method !== m.id ? 0.78 : 1,
          }}>
          <div className="row" style={{justifyContent:'space-between', marginBottom: 6}}>
            <span className="display" style={{fontSize: 22}}>
              {m.name}
            </span>
            <div className="row" style={{gap: 6}}>
              {m.badge === 'pro' && <ProDot />}
              <span style={{
                width: 18, height: 18, borderRadius: 999,
                border: '2px solid ' + (method === m.id ? 'var(--accent)' : 'var(--border-strong)'),
                background: method === m.id ? 'var(--accent)' : 'transparent',
              }}></span>
            </div>
          </div>
          <p className="muted" style={{margin: 0, fontSize: 13, lineHeight: 1.45}}>{m.desc}</p>
        </button>
      ))}
    </div>
  );
}

function UsageStrip({ lines, rowLimit }) {
  const overRows = lines > rowLimit;
  return (
    <div style={{
      padding: '12px 28px',
      background: 'var(--bg-card-2)',
      borderBottom: '1px solid var(--border)',
      display: 'grid', gridTemplateColumns: '1fr auto', gap: 24, alignItems: 'center',
    }}>
      <UsageBar label="Строк введено" current={lines} max={rowLimit} over={overRows} unit="" />
      <a href="#" onClick={(e) => { e.preventDefault(); document.querySelector('[data-api-promo]')?.scrollIntoView({behavior:'smooth', block:'center'}); }}
        className="btn btn-soft btn-sm">
        Снять лимиты <ProDot />
      </a>
    </div>
  );
}

function UsageBar({ label, current, max, over, unit = '' }) {
  const pct = Math.min(100, (current / max) * 100);
  return (
    <div>
      <div className="row" style={{justifyContent: 'space-between', marginBottom: 6}}>
        <span style={{fontSize: 11, textTransform: 'uppercase', letterSpacing: '.1em', color: 'var(--text-dim)'}}>{label}</span>
        <span className="mono" style={{fontSize: 12, color: over ? '#E26C6C' : 'var(--text)'}}>
          {current}{unit} / {max}{unit}
        </span>
      </div>
      <div style={{height: 4, background: 'var(--bg-input)', borderRadius: 999, overflow: 'hidden'}}>
        <div style={{
          width: pct + '%', height: '100%',
          background: over ? '#E26C6C' : pct > 80 ? 'var(--accent)' : 'var(--accent)',
          opacity: pct > 80 ? 1 : 0.7,
          transition: '200ms',
        }}></div>
      </div>
    </div>
  );
}

function PhraseInput({ phrases, setPhrases, rowLimit, onFileDrop, preset, setPreset }) {
  const [drag, setDrag] = useState(false);
  const ref = useRef(null);

  const handleDrop = (e) => {
    e.preventDefault(); setDrag(false);
    const f = e.dataTransfer.files[0];
    if (f) onFileDrop(f);
  };
  const insertExample = (kind) => {
    const examples = {
      ecommerce: sampleData.phrases,
      services: 'натяжные потолки\nнатяжные потолки цена\nнатяжные потолки в спб\nматовые натяжные потолки\nглянцевые натяжные потолки\nдвухуровневые потолки\nремонт квартиры\nремонт квартиры под ключ\nремонт ванной\nремонт ванной комнаты',
      info: 'как почистить ноутбук\nкак почистить клавиатуру\nкак почистить экран\nкак ускорить ноутбук\nкак ускорить компьютер\nпочему ноутбук тормозит\nпочему компьютер шумит\nкак выбрать ноутбук\nкак выбрать компьютер',
    };
    setPhrases(examples[kind]);
    setPreset(kind);
  };

  const linesCount = phrases.split('\n').filter(l => l.trim()).length;
  const overLimit = linesCount > rowLimit;

  return (
    <div>
      <div className="row" style={{justifyContent: 'space-between', marginBottom: 10, flexWrap: 'wrap', gap: 8}}>
        <span className="field-label" style={{margin: 0}}>Список фраз — по одной на строку</span>
        <div className="row" style={{gap: 4, fontSize: 12}}>
          <span className="muted" style={{marginRight: 6}}>Пресет:</span>
          {[
            { id: 'ecommerce', name: 'E-commerce' },
            { id: 'services', name: 'Услуги' },
            { id: 'info', name: 'Инфо-сайт' },
          ].map(p => (
            <button key={p.id}
              className={'btn btn-sm ' + (preset === p.id ? 'btn-pri' : 'btn-soft')}
              onClick={() => insertExample(p.id)}>
              {p.name}
            </button>
          ))}
          <span style={{width: 1, height: 18, background: 'var(--border)', margin: '0 6px'}}></span>
          <button className="btn btn-soft btn-sm" onClick={() => setPhrases('')}>Очистить</button>
        </div>
      </div>
      <div
        onDragOver={(e) => { e.preventDefault(); setDrag(true); }}
        onDragLeave={() => setDrag(false)}
        onDrop={handleDrop}
        style={{position: 'relative'}}>
        <textarea
          ref={ref}
          className="textarea"
          style={{
            minHeight: 280,
            borderColor: overLimit ? '#E26C6C' : drag ? 'var(--accent)' : 'var(--border)',
            background: drag ? 'var(--accent-soft)' : 'var(--bg-input)',
          }}
          value={phrases}
          onChange={(e) => setPhrases(e.target.value)}
          placeholder="купить пылесос&#10;робот-пылесос&#10;лучшие роботы пылесосы&#10;ручной пылесос&#10;..." />
        {drag && (
          <div style={{
            position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center',
            pointerEvents: 'none', borderRadius: 'var(--radius)',
            background: 'var(--accent-soft)', color: 'var(--accent)',
            font: 'inherit', fontWeight: 700, fontSize: 18, letterSpacing: '0.04em',
            border: '2px dashed var(--accent)',
          }}>
            ↓ Бросьте файл .txt или .csv
          </div>
        )}
        <div style={{
          position: 'absolute', bottom: 12, right: 12,
          display: 'flex', alignItems: 'center', gap: 12,
          fontSize: 11, color: overLimit ? '#E26C6C' : 'var(--text-mute)',
          background: 'var(--bg-input)', padding: '4px 10px', borderRadius: 999,
          border: '1px solid var(--border)',
        }}>
          <span className="mono">{linesCount} / {rowLimit}</span>
          {overLimit && <span style={{fontWeight: 600}}>· превышен лимит</span>}
        </div>
      </div>
    </div>
  );
}

function SettingsStrip({
  method, strength, setStrength, minSize, setMinSize,
  ignoreWords, setIgnoreWords, advancedOpen, setAdvancedOpen,
  useEmbeddings, setUseEmbeddings, isAdmin, onProClick,
}) {
  return (
    <div style={{marginTop: 22}}>
      <div style={{display: 'grid', gridTemplateColumns: '2fr 1.4fr 1fr', gap: 20, alignItems: 'end'}}>
        <div>
          <div className="row" style={{justifyContent: 'space-between', marginBottom: 10}}>
            <label className="field-label" style={{margin: 0}}>Сила группировки</label>
            <span className="mono" style={{fontSize: 13, color: 'var(--accent)', fontWeight: 600}}>{strength}</span>
          </div>
          <div className="row" style={{gap: 4}}>
            {[3, 4, 5, 6, 7].map(s => (
              <button
                key={s}
                onClick={() => setStrength(s)}
                style={{
                  flex: 1, height: 44, border: '1px solid var(--border)',
                  background: strength === s ? 'var(--accent)' : 'var(--bg-input)',
                  color: strength === s ? 'var(--accent-text)' : 'var(--text)',
                  borderRadius: 12, font: 'inherit', fontWeight: 600,
                  fontSize: 13, cursor: 'pointer',
                  borderColor: strength === s ? 'var(--accent)' : 'var(--border)',
                }}>
                {s}
              </button>
            ))}
          </div>
          <div className="row" style={{justifyContent: 'space-between', fontSize: 11, color: 'var(--text-mute)', marginTop: 6}}>
            <span>широкие группы</span>
            <span>точные группы</span>
          </div>
        </div>
        <div>
          <label className="field-label">Игнорируемые слова</label>
          <input className="input" value={ignoreWords} onChange={(e) => setIgnoreWords(e.target.value)} placeholder="купить, цена, недорого" />
        </div>
        <div>
          <label className="field-label">Мин. фраз в группе</label>
          <input className="input" type="number" min="1" value={minSize} onChange={(e) => setMinSize(parseInt(e.target.value)||1)} />
        </div>
      </div>

      <button
        onClick={() => setAdvancedOpen(!advancedOpen)}
        className="btn btn-ghost btn-sm"
        style={{marginTop: 16, border: 'none', padding: '8px 0', color: 'var(--text-dim)'}}>
        {advancedOpen ? '▾' : '▸'}  Расширенные настройки
      </button>
      {advancedOpen && (
        <div style={{
          marginTop: 12, padding: 18,
          border: '1px solid var(--border)', borderRadius: 14,
          background: 'var(--bg-input)',
          display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 18,
        }}>
          {isAdmin ? (
            <label className="check">
              <input type="checkbox" checked={useEmbeddings} onChange={(e)=>setUseEmbeddings(e.target.checked)} />
              <span className="box"></span>
              Точная кластеризация (OpenAI embeddings) <ProDot />
            </label>
          ) : (
            <label className="check" style={{opacity: 0.65}} onClick={(e)=>{ e.preventDefault(); onProClick('Точная кластеризация (OpenAI embeddings)'); }}>
              <input type="checkbox" disabled />
              <span className="box"></span>
              Точная кластеризация (OpenAI embeddings) <ProDot />
            </label>
          )}
          <label className="check" style={{opacity: 0.65}} onClick={(e)=>{ e.preventDefault(); onProClick('Импорт частотностей из Wordstat'); }}>
            <input type="checkbox" disabled /><span className="box"></span>
            Дотянуть частотности из Wordstat <ProDot />
          </label>
          <label className="check" style={{opacity: 0.65}} onClick={(e)=>{ e.preventDefault(); onProClick('Webhook на завершение'); }}>
            <input type="checkbox" disabled /><span className="box"></span>
            Webhook на завершение <ProDot />
          </label>
        </div>
      )}
    </div>
  );
}

/* ============ Results ============ */

function ResultsView({
  results, allResults, view, setView, search, setSearch,
  intentFilter, setIntentFilter, intentCounts,
  sortOrder, setSortOrder, openGroups, toggleGroup,
  onRename, onMovePhrase, onDeleteGroup, onMergeGroups, onProClick,
}) {
  return (
    <>
      {/* Toolbar */}
      <div className="card card-tight" style={{marginBottom: 16, padding: 14}}>
        <div className="row" style={{gap: 12, flexWrap: 'wrap'}}>
          <div className="row" style={{gap: 2, padding: 3, background: 'var(--bg-input)', borderRadius: 10}}>
            {[
              { id: 'cards', label: 'Карточки' },
              { id: 'table', label: 'Таблица' },
              { id: 'graph', label: 'Граф', pro: true },
            ].map(v => (
              <button key={v.id}
                onClick={() => v.pro ? onProClick('Граф связей') : setView(v.id)}
                style={{
                  padding: '8px 14px', border: 0, font: 'inherit', fontSize: 13,
                  borderRadius: 8, cursor: 'pointer', fontWeight: 500,
                  background: view === v.id ? 'var(--bg-card)' : 'transparent',
                  color: view === v.id ? 'var(--text)' : 'var(--text-dim)',
                  display: 'inline-flex', alignItems: 'center', gap: 6,
                }}>
                {v.label} {v.pro && <ProDot />}
              </button>
            ))}
          </div>
          <input className="input" style={{flex: 1, minWidth: 200, height: 38, padding: '8px 14px'}}
            placeholder="🔍 Поиск по результатам…" value={search} onChange={(e) => setSearch(e.target.value)} />
          <select className="select" style={{width: 200, height: 38, padding: '8px 14px'}} value={sortOrder} onChange={(e) => setSortOrder(e.target.value)}>
            <option value="countDesc">Размер ↓</option>
            <option value="countAsc">Размер ↑</option>
            <option value="freqDesc">Частотность ↓</option>
            <option value="nameAsc">А → Я</option>
          </select>
        </div>
        <div className="row" style={{gap: 6, marginTop: 12, flexWrap: 'wrap'}}>
          {[
            { id: 'all', label: 'Все' },
            ...INTENT_RULES.map(r => ({ id: r.kind, label: r.label, color: r.color })),
          ].map(f => (
            <button key={f.id}
              className={'btn btn-sm ' + (intentFilter === f.id ? '' : 'btn-soft')}
              onClick={() => setIntentFilter(f.id)}
              style={intentFilter === f.id ? {
                background: f.color || 'var(--accent)', color: '#fff',
              } : {}}>
              {f.label}{intentCounts[f.id] !== undefined && <span style={{opacity: .7, marginLeft: 6}}>{intentCounts[f.id]||0}</span>}
            </button>
          ))}
        </div>
      </div>

      <div className="row muted" style={{marginBottom: 12, justifyContent: 'space-between'}}>
        <span>Найдено групп: <b style={{color: 'var(--text)'}}>{results.length}</b> · {results.reduce((a,g)=>a+g.size,0)} фраз</span>
      </div>

      {view === 'cards' && (
        <div className="col" style={{gap: 10}}>
          {results.map((g, i) => (
            <ClusterCard key={g.name + i}
              g={g} index={i+1}
              open={openGroups.has(g.name)}
              onToggle={() => toggleGroup(g.name)}
              highlight={search}
              onRename={onRename}
              onMovePhrase={onMovePhrase}
              onDelete={onDeleteGroup}
              onMerge={onMergeGroups}
              allGroups={allResults}
            />
          ))}
        </div>
      )}
      {view === 'table' && <ResultsTable results={results} />}
      {results.length === 0 && (
        <div className="card" style={{textAlign: 'center', color: 'var(--text-dim)'}}>
          Ничего не найдено по фильтру.
        </div>
      )}
    </>
  );
}

function ClusterCard({ g, index, open, onToggle, highlight, onRename, onMovePhrase, onDelete, onMerge, allGroups }) {
  const [editing, setEditing] = useState(false);
  const [draftName, setDraftName] = useState(g.name);
  const [menuOpen, setMenuOpen] = useState(false);
  const [movingPhrase, setMovingPhrase] = useState(null);

  const hl = (s) => {
    if (!highlight) return s;
    const re = new RegExp(`(${highlight.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'ig');
    return s.split(re).map((p, i) => re.test(p) ?
      <mark key={i} style={{background: 'var(--accent-soft)', color: 'var(--accent)', padding: '0 2px', borderRadius: 3}}>{p}</mark> : p);
  };

  const otherGroups = allGroups.filter(x => x.name !== g.name);

  return (
    <div className="card" style={{padding: 0, overflow: 'visible'}}>
      <div style={{display: 'flex', alignItems: 'center', gap: 14, padding: '18px 22px', cursor: 'pointer'}} onClick={onToggle}>
        <span style={{
          width: 44, height: 44, borderRadius: 12, flexShrink: 0,
          background: 'var(--accent-soft)',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
        }}>
          <span style={{color: 'var(--accent)', fontWeight: 700, fontSize: 16}}>{g.size}</span>
        </span>
        <div style={{flex: 1, minWidth: 0}}>
          {editing ? (
            <input
              autoFocus className="input" style={{padding: '6px 10px', fontSize: 18, fontWeight: 700}}
              value={draftName}
              onClick={(e) => e.stopPropagation()}
              onChange={(e) => setDraftName(e.target.value)}
              onKeyDown={(e) => {
                if (e.key === 'Enter') { onRename(g.name, draftName); setEditing(false); }
                if (e.key === 'Escape') { setDraftName(g.name); setEditing(false); }
              }}
              onBlur={() => { onRename(g.name, draftName); setEditing(false); }} />
          ) : (
            <div className="display" style={{
              fontSize: 19, textTransform: 'uppercase', letterSpacing: '0.03em',
              whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
            }}>
              {hl(g.name)}
            </div>
          )}
          <div className="row" style={{gap: 6, marginTop: 8, flexWrap: 'wrap'}}>
            <span className="chip" style={{background: g.intent.color + '22', color: g.intent.color, borderColor: 'transparent'}}>
              {g.intent.label}
            </span>
            <span className="chip mono">~{fmtFreq(g.freq)} /мес</span>
            <span className="chip mono dim">{g.url}</span>
          </div>
        </div>
        <div className="row" style={{gap: 4, position: 'relative'}}>
          <button
            className="btn btn-soft btn-sm"
            onClick={(e) => { e.stopPropagation(); setEditing(true); }}
            title="Переименовать главную фразу">
            ✎
          </button>
          <button
            className="btn btn-soft btn-sm"
            onClick={(e) => { e.stopPropagation(); setMenuOpen(!menuOpen); }}>
            ⋯
          </button>
          {menuOpen && (
            <div onClick={(e) => e.stopPropagation()} style={{
              position: 'absolute', top: '100%', right: 0, marginTop: 4, zIndex: 30,
              background: 'var(--bg-elev)', border: '1px solid var(--border-strong)',
              borderRadius: 12, padding: 6, minWidth: 220,
              boxShadow: '0 10px 30px rgba(0,0,0,.4)',
            }}>
              <MenuItem onClick={() => { setEditing(true); setMenuOpen(false); }}>✎ Переименовать главную</MenuItem>
              <div style={{padding: '6px 10px', fontSize: 11, color: 'var(--text-mute)', textTransform: 'uppercase', letterSpacing: '.1em'}}>Объединить с</div>
              {otherGroups.slice(0, 4).map(og => (
                <MenuItem key={og.name} onClick={() => { onMerge(g.name, og.name); setMenuOpen(false); }}>
                  ↳ {og.name}
                </MenuItem>
              ))}
              {otherGroups.length === 0 && <div style={{padding: '6px 10px', color: 'var(--text-mute)', fontSize: 12}}>нет других групп</div>}
              <div style={{height: 1, background: 'var(--border)', margin: '4px 0'}}></div>
              <MenuItem danger onClick={() => { onDelete(g.name); setMenuOpen(false); }}>🗑 Удалить группу</MenuItem>
            </div>
          )}
          <span style={{
            width: 32, height: 32, borderRadius: 999, marginLeft: 4,
            background: 'var(--bg-card-2)', display: 'inline-flex',
            alignItems: 'center', justifyContent: 'center',
            transform: open ? 'rotate(180deg)' : 'rotate(0)',
            transition: '180ms', flexShrink: 0,
          }}>▾</span>
        </div>
      </div>
      {open && (
        <div style={{padding: '4px 22px 18px', borderTop: '1px solid var(--border)'}}>
          {g.phrases.map((p, i) => (
            <div key={i} style={{
              padding: '10px 0', borderBottom: i < g.phrases.length - 1 ? '1px solid var(--border)' : 0,
              display: 'flex', alignItems: 'center', gap: 12, fontSize: 14, position: 'relative',
            }}>
              <span className="mono dim" style={{fontSize: 11, width: 28}}>{String(i+1).padStart(2,'0')}</span>
              <span style={{flex: 1}}>{hl(p)}</span>
              <span className="mono dim" style={{fontSize: 11}}>~{fmtFreq(estimateFreq(p))}</span>
              {i === 0 && <span className="chip chip-accent" style={{fontSize: 10}}>main</span>}
              <button className="btn btn-soft btn-sm" style={{padding: '4px 8px', fontSize: 11}}
                onClick={() => setMovingPhrase(movingPhrase === p ? null : p)}>
                ↗ перенести
              </button>
              {movingPhrase === p && (
                <div style={{
                  position: 'absolute', right: 0, top: '100%', zIndex: 30, marginTop: 4,
                  background: 'var(--bg-elev)', border: '1px solid var(--border-strong)',
                  borderRadius: 10, padding: 6, minWidth: 220,
                  boxShadow: '0 10px 30px rgba(0,0,0,.4)',
                }}>
                  {otherGroups.slice(0, 6).map(og => (
                    <MenuItem key={og.name} onClick={() => { onMovePhrase(g.name, p, og.name); setMovingPhrase(null); }}>
                      → {og.name}
                    </MenuItem>
                  ))}
                  {otherGroups.length === 0 && <div style={{padding: '6px 10px', color: 'var(--text-mute)', fontSize: 12}}>нет других групп</div>}
                </div>
              )}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

function MenuItem({ children, onClick, danger }) {
  return (
    <button onClick={onClick} style={{
      display: 'block', width: '100%', textAlign: 'left',
      padding: '8px 10px', borderRadius: 8, border: 0, background: 'transparent',
      color: danger ? '#E26C6C' : 'var(--text)', font: 'inherit', fontSize: 13, cursor: 'pointer',
    }} onMouseOver={(e) => e.currentTarget.style.background = 'var(--bg-card-2)'}
       onMouseOut={(e) => e.currentTarget.style.background = 'transparent'}>
      {children}
    </button>
  );
}

function ResultsTable({ results }) {
  return (
    <div className="card" style={{padding: 0, overflow: 'hidden'}}>
      <table className="table">
        <thead>
          <tr>
            <th style={{width: 60}}>#</th>
            <th>Главная фраза</th>
            <th>Intent</th>
            <th style={{textAlign: 'right'}}>Фраз</th>
            <th style={{textAlign: 'right'}}>~частот.</th>
            <th>URL</th>
          </tr>
        </thead>
        <tbody>
          {results.map((g, i) => (
            <tr key={i}>
              <td className="mono dim">{String(i+1).padStart(2,'0')}</td>
              <td><b>{g.name}</b></td>
              <td><span className="chip" style={{background: g.intent.color + '22', color: g.intent.color, borderColor: 'transparent'}}>{g.intent.label}</span></td>
              <td className="mono" style={{textAlign: 'right'}}>{g.size}</td>
              <td className="mono" style={{textAlign: 'right'}}>{fmtFreq(g.freq)}</td>
              <td className="mono dim">{g.url}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

/* ============ Empty / Promo / Features ============ */

function EmptyState() {
  return (
    <div className="card card-section" style={{textAlign:'center', borderStyle: 'dashed', padding: '48px 32px'}}>
      <div className="display" style={{fontSize: 28, marginBottom: 10}}>Готов к работе</div>
      <p className="muted" style={{maxWidth: 480, margin: '0 auto 16px'}}>
        Введите фразы, выберите метод и нажмите «Сгруппировать». Результат появится здесь.
      </p>
      <div className="row" style={{justifyContent:'center', gap: 8, fontSize: 12, color:'var(--text-mute)'}}>
        <span className="kbd">⌘ ⏎</span><span>горячая клавиша для запуска</span>
      </div>
    </div>
  );
}

function ApiPromo({ onProClick }) {
  return (
    <div data-api-promo style={{
      marginTop: 56, padding: 36, borderRadius: 24,
      background: 'linear-gradient(135deg, var(--accent-soft), transparent 60%), var(--bg-card)',
      border: '1px solid var(--border)',
      display: 'grid', gridTemplateColumns: '1.2fr 1fr', gap: 32,
    }}>
      <div>
        <div className="eyebrow" style={{color: 'var(--accent)'}}>· pro · API доступ</div>
        <h2 className="display" style={{fontSize: 36, margin: '8px 0 14px'}}>
          Кластеризация прямо<br/>из вашего пайплайна
        </h2>
        <p className="muted" style={{fontSize: 15, maxWidth: 460, margin: '0 0 20px'}}>
          Без лимита на фразы и запуски. Webhook о готовности, история запусков, командный доступ
          и приоритетная очередь. Тариф включён в подписку «Инструменты SAVOV».
        </p>
        <ul className="col" style={{gap: 8, listStyle: 'none', padding: 0, margin: '0 0 20px', fontSize: 14}}>
          {[
            'До 200 000 фраз в одном запросе',
            'Webhook + интеграции (Make, n8n, Zapier)',
            'Метод SERP по топ-10 Яндекса',
            'Импорт частотностей Wordstat',
            'Командный доступ и аудит-логи',
          ].map((t, i) => (
            <li key={i} className="row" style={{gap: 10}}>
              <span style={{color: 'var(--accent)', fontWeight: 700}}>✓</span> {t}
            </li>
          ))}
        </ul>
        <div className="row" style={{gap: 8}}>
          <button className="btn btn-pri" onClick={() => onProClick('API доступ')}>Подключить API</button>
          <a href="#" className="btn btn-ghost">Документация</a>
        </div>
      </div>
      <div className="card" style={{
        padding: 18, fontFamily: 'var(--font-mono)', fontSize: 12.5, lineHeight: 1.7,
        background: 'var(--bg-input)', overflow: 'hidden',
      }}>
        <div className="row" style={{gap: 6, marginBottom: 12}}>
          <span style={{width: 10, height: 10, borderRadius: 999, background: '#E26C6C'}}></span>
          <span style={{width: 10, height: 10, borderRadius: 999, background: '#F5821F'}}></span>
          <span style={{width: 10, height: 10, borderRadius: 999, background: '#3FBF87'}}></span>
          <span className="dim" style={{marginLeft: 8, fontSize: 11}}>cURL</span>
        </div>
        <pre style={{margin: 0, color: 'var(--text)', whiteSpace: 'pre-wrap'}}>
<span style={{color: '#9B7CF6'}}>curl</span> -X POST https://api.savov.ru/v1/cluster \{'\n'}
  -H <span style={{color: '#3FBF87'}}>"Authorization: Bearer $SAVOV_TOKEN"</span> \{'\n'}
  -H <span style={{color: '#3FBF87'}}>"Content-Type: application/json"</span> \{'\n'}
  -d <span style={{color: '#3FBF87'}}>'{'{\n    "method": "serp",\n    "strength": 4,\n    "phrases": [\n      "купить пылесос",\n      "робот-пылесос xiaomi",\n      "лучшие моющие пылесосы"\n    ],\n    "callback": "https://hooks.app/clusters"\n  }'}</span>
        </pre>
        <div style={{height: 1, background: 'var(--border)', margin: '14px 0'}}></div>
        <span className="dim" style={{fontSize: 11}}>← 202 Accepted</span>
        <pre style={{margin: '6px 0 0', color: 'var(--text-dim)'}}>{'{\n  "run_id": "run_a8f2...",\n  "status": "queued",\n  "eta_sec": 4\n}'}</pre>
      </div>
    </div>
  );
}

function FeatureGrid() {
  const items = [
    { t: 'Точность 94%', d: 'Лемматизация под русский. Учитывает склонения, опечатки и брендовые написания.' },
    { t: '3 метода', d: 'Soft, Hard и SERP-кластеризация. Выбирайте под нишу и характер запросов.' },
    { t: 'Intent + частотность', d: 'Каждой группе — тип запроса и оценка частотности. Готовая основа под seo-структуру.' },
    { t: 'Управление кластерами', d: 'Переименовывайте главные фразы, перетаскивайте запросы, объединяйте и разделяйте группы прямо в результатах.' },
  ];
  return (
    <div id="features" style={{marginTop: 64}}>
      <div className="eyebrow" style={{color: 'var(--accent)'}}>· возможности</div>
      <h2 className="display" style={{fontSize: 38, margin: '8px 0 24px', maxWidth: 700}}>
        Сделан SEO-командой,<br/>
        <span style={{color: 'var(--text-dim)'}}>а не теоретиками от ML</span>
      </h2>
      <div style={{display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 12}}>
        {items.map((f, i) => (
          <div key={i} className="card" style={{padding: 22, minHeight: 200, display: 'flex', flexDirection: 'column', justifyContent: 'space-between'}}>
            <span className="display" style={{fontSize: 22, lineHeight: 1.1}}>{f.t}</span>
            <p className="muted" style={{margin: 0, fontSize: 13, lineHeight: 1.5}}>{f.d}</p>
          </div>
        ))}
      </div>
    </div>
  );
}

/* ============ Paywall + Tweaks ============ */

function PaywallModal({ feature, onClose }) {
  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, background: 'rgba(0,0,0,.7)',
      backdropFilter: 'blur(8px)', zIndex: 200,
      display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24,
    }}>
      <div onClick={(e) => e.stopPropagation()} className="card" style={{
        padding: 36, maxWidth: 520, width: '100%',
        background: 'var(--bg-elev)', borderColor: 'var(--accent)',
      }}>
        <div className="row" style={{justifyContent: 'space-between', marginBottom: 14}}>
          <ProDot />
          <button className="btn btn-soft btn-sm" onClick={onClose}>×</button>
        </div>
        <h2 className="display" style={{fontSize: 28, margin: '0 0 10px'}}>{feature} — Pro-функция</h2>
        <p className="muted" style={{margin: '0 0 20px', fontSize: 15}}>
          Доступна на плане «Инструменты SAVOV». Подключите подписку — получите все инструменты,
          API-доступ, увеличенные лимиты и приоритетную поддержку.
        </p>
        <div style={{display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 8, marginBottom: 22}}>
          {['SERP-кластеризация', 'API + Webhook', '200K фраз в запросе', 'Wordstat-частотности', 'Граф связей', 'Командный доступ'].map((t,i) => (
            <div key={i} className="row" style={{gap: 8, fontSize: 13}}>
              <span style={{color: 'var(--accent)'}}>✓</span> {t}
            </div>
          ))}
        </div>
        <div className="row" style={{gap: 8}}>
          <button className="btn btn-pri" style={{flex: 1}}>Подключить подписку</button>
          <button className="btn btn-ghost" onClick={onClose}>Позже</button>
        </div>
      </div>
    </div>
  );
}

function TweakPanel({ tweaks, onClose }) {
  const accents = [
    { name: 'orange', val: '#F5821F' },
    { name: 'blue',   val: '#5B8DEF' },
    { name: 'green',  val: '#3FBF87' },
    { name: 'pink',   val: '#E26CB6' },
    { name: 'violet', val: '#9B7CF6' },
  ];
  return (
    <div style={{
      position: 'fixed', right: 24, bottom: 24, width: 280,
      background: 'var(--bg-elev)', border: '1px solid var(--border-strong)',
      borderRadius: 16, padding: 20, zIndex: 100,
      boxShadow: '0 20px 60px rgba(0,0,0,0.5)',
    }}>
      <div className="row" style={{justifyContent: 'space-between', marginBottom: 14}}>
        <span className="display" style={{fontSize: 16}}>Tweaks</span>
        <button className="btn btn-soft btn-sm" onClick={onClose}>×</button>
      </div>
      <div className="field">
        <label className="field-label">Акцент</label>
        <div className="row" style={{gap: 8}}>
          {accents.map(a => (
            <button key={a.name} onClick={() => tweaks.setAccent(a.val)}
              style={{
                width: 32, height: 32, borderRadius: 999,
                border: tweaks.accent === a.val ? '2px solid var(--text)' : '2px solid transparent',
                background: a.val, cursor: 'pointer', padding: 0,
              }} />
          ))}
        </div>
      </div>
      <div className="field" style={{marginBottom: 0}}>
        <label className="field-label">Тема</label>
        <div className="row" style={{gap: 8}}>
          <button className={'btn btn-sm ' + (tweaks.theme === 'dark' ? 'btn-pri' : 'btn-soft')} onClick={() => tweaks.setTheme('dark')} style={{flex: 1}}>Тёмная</button>
          <button className={'btn btn-sm ' + (tweaks.theme === 'light' ? 'btn-pri' : 'btn-soft')} onClick={() => tweaks.setTheme('light')} style={{flex: 1}}>Светлая</button>
        </div>
      </div>
    </div>
  );
}

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