const { useState, useMemo, useEffect, useRef } = React;

const STEPS = [
  { key: 'source',  title: 'Что анализируем?',   desc: 'URL или скриншоты' },
  { key: 'pain',    title: 'Что не устраивает?', desc: 'Опишите задачу' },
  { key: 'context', title: 'Контекст',           desc: 'Трафик и аудитория' },
  { key: 'contact', title: 'Контакты',           desc: 'Куда прислать отчёт' },
  { key: 'review',  title: 'Проверка',           desc: 'Сводка и отправка' },
];

const TRAFFIC_SOURCES = [
  { v: 'organic',  label: 'Поисковый трафик',     sub: 'SEO, органика' },
  { v: 'paid',     label: 'Платная реклама',      sub: 'Контекст, таргет' },
  { v: 'social',   label: 'Соцсети и контент',    sub: 'SMM, блог, YouTube' },
  { v: 'direct',   label: 'Прямой / реферальный', sub: 'Закладки, ссылки' },
  { v: 'email',    label: 'Email и рассылки',     sub: 'CRM, рассылки, push' },
  { v: 'none',     label: 'Трафика пока нет',     sub: 'Готовимся к запуску' },
];

const TRAFFIC_VOLUMES = ['< 500 / мес', '500 – 5 000', '5 000 – 50 000', '> 50 000', 'Не знаю / не считал'];

const PROJECT_TYPES = ['SaaS', 'E-commerce', 'Сервис / услуги', 'Образование', 'Финтех', 'Медиа / контент', 'Другое'];

const PAIN_HINTS = [
  'Трафик есть, заявок мало',
  'Низкая конверсия в покупку',
  'Не понимаю, где UX-проблемы',
  'Готовимся к редизайну',
  'Падает доходимость в воронке',
  'Нужен внешний независимый взгляд',
];

const initial = {
  sourceType: 'url',
  url: '',
  files: [],
  pain: '',
  trafficSource: [],
  trafficVolume: '',
  projectType: '',
  audience: '',
  email: '',
  telegram: '',
  consent: false,
};

const API_URL = '/api/lead';

async function postJSON(payload) {
  const res = await fetch(API_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });
  let body = {};
  try { body = await res.json(); } catch (e) { /* пустой ответ */ }
  if (!res.ok) throw new Error(body.error || 'Сервер недоступен, попробуйте позже');
  return body;
}

// Ужимаем скриншот в браузере: у Vercel лимит тела запроса ~4.5 МБ, а base64
// раздувает вес ещё на треть. Держим ширину до 1920 и снижаем качество, пока
// не уложимся в лимит — текст на скринах должен остаться читаемым.
const MAX_WIDTH = 1920;
const MAX_BASE64_BYTES = 2.8 * 1024 * 1024;

function compressImage(file) {
  return new Promise((resolve, reject) => {
    const url = URL.createObjectURL(file);
    const img = new Image();
    img.onload = () => {
      URL.revokeObjectURL(url);
      const scale = Math.min(1, MAX_WIDTH / img.width);
      const canvas = document.createElement('canvas');
      canvas.width = Math.round(img.width * scale);
      canvas.height = Math.round(img.height * scale);
      canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height);

      let base64 = '';
      for (const q of [0.85, 0.75, 0.6, 0.45, 0.3]) {
        base64 = canvas.toDataURL('image/jpeg', q).split(',')[1] || '';
        if (base64.length <= MAX_BASE64_BYTES) break;
      }
      if (!base64) { reject(new Error('Не удалось обработать ' + file.name)); return; }
      if (base64.length > MAX_BASE64_BYTES) {
        reject(new Error(`Скриншот «${file.name}» слишком большой — уменьшите его и попробуйте снова`));
        return;
      }
      resolve({ base64, name: file.name.replace(/\.[^.]+$/, '') + '.jpg' });
    };
    img.onerror = () => {
      URL.revokeObjectURL(url);
      reject(new Error(`Не удалось прочитать «${file.name}» — это точно изображение?`));
    };
    img.src = url;
  });
}

function formatBytes(b) {
  if (b < 1024) return b + ' B';
  if (b < 1024 * 1024) return (b/1024).toFixed(0) + ' KB';
  return (b/(1024*1024)).toFixed(1) + ' MB';
}

function validURL(u) {
  if (!u || u.trim().length < 4) return false;
  try {
    let s = u.trim();
    if (!/^https?:\/\//i.test(s)) s = 'https://' + s;
    const url = new URL(s);
    return !!url.hostname && url.hostname.includes('.');
  } catch { return false; }
}
function validEmail(e) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test((e||'').trim());
}
function validTelegram(t) {
  if (!t) return true;
  let s = t.trim();
  // strip a full/partial URL down to the username
  s = s.replace(/^https?:\/\//i, '').replace(/^t\.me\//i, '').replace(/^@/, '');
  s = s.replace(/\/+$/, ''); // trailing slash(es)
  // valid Telegram username: 5–32 chars, letters/digits/underscore, must contain a letter
  return /^[a-zA-Z0-9_]{5,32}$/.test(s) && /[a-zA-Z]/.test(s);
}

function App() {
  const [step, setStep] = useState(0);
  const [data, setData] = useState(() => {
    const params = new URLSearchParams(window.location.search);
    const preUrl = params.get('url');
    const preSource = params.get('source');
    if (preSource === 'files') {
      let files = [];
      try {
        const stored = JSON.parse(sessionStorage.getItem('ux_radar_files') || '[]');
        files = stored.map(f => {
          const arr = f.data.split(',');
          const mime = arr[0].match(/:(.*?);/)[1];
          const bstr = atob(arr[1]);
          const u8 = new Uint8Array(bstr.length);
          for (let i = 0; i < bstr.length; i++) u8[i] = bstr.charCodeAt(i);
          return new File([u8], f.name, { type: mime });
        });
        sessionStorage.removeItem('ux_radar_files');
      } catch (e) {
        files = [];
      }
      // Даже если файлы не доехали, остаёмся на вкладке «Скриншоты»: человек
      // пришёл сюда именно с ними, и пустое поле URL его только запутает.
      return { ...initial, sourceType: 'files', files };
    }
    return preUrl ? { ...initial, url: preUrl } : initial;
  });
  const [submitted, setSubmitted] = useState(false);
  const [submitError, setSubmitError] = useState('');
  const [uploading, setUploading] = useState(null); // {done, total} — прогресс скриншотов
  const [hp, setHp] = useState('');                 // honeypot: заполняют только боты
  const startedAt = useRef(Date.now());
  const [touched, setTouched] = useState({});
  const [saved, setSaved] = useState('Готово к заполнению');
  const saveTimer = useRef(null);

  const set = (patch) => {
    setData(d => ({ ...d, ...patch }));
    setSaved('Сохраняем…');
    clearTimeout(saveTimer.current);
    saveTimer.current = setTimeout(() => setSaved('Черновик сохранён'), 700);
  };

  // Validation for the current step
  const stepErrors = useMemo(() => {
    const e = {};
    if (step === 0) {
      if (data.sourceType === 'url') {
        if (!data.url.trim()) e.url = 'Укажите ссылку на страницу';
        else if (!validURL(data.url)) e.url = 'Похоже, это не URL — проверьте формат';
      } else {
        if (data.files.length === 0) e.files = 'Загрузите хотя бы один скриншот';
      }
    }
    if (step === 1) {
      if (data.pain.trim().length < 12) e.pain = 'Несколько предложений помогут точнее понять задачу';
    }
    if (step === 2) {
      if (!data.trafficSource || !data.trafficSource.length) e.trafficSource = 'Выберите хотя бы один канал';
      if (!data.trafficVolume) e.trafficVolume = 'Укажите примерный объём';
    }
    if (step === 3) {
      if (!validEmail(data.email)) e.email = 'Введите корректный email';
      if (!validTelegram(data.telegram)) e.telegram = 'Телеграм-ник или ссылка t.me/…';
      if (!data.consent) e.consent = 'Нужно согласие на обработку';
    }
    return e;
  }, [step, data]);

  const stepValid = Object.keys(stepErrors).length === 0;

  // Mark fields touched only when user tries to advance
  const next = () => {
    if (!stepValid) {
      const t = {};
      Object.keys(stepErrors).forEach(k => t[k] = true);
      setTouched(touched => ({ ...touched, ...t }));
      return;
    }
    setTouched({});
    if (step < STEPS.length - 1) setStep(step + 1);
    else submit();
  };

  const prev = () => { setTouched({}); if (step > 0) setStep(step - 1); };

  const submit = async () => {
    setSubmitted(true);
    setSubmitError('');

    try {
      const trafficLabels = (data.trafficSource || [])
        .map(v => (TRAFFIC_SOURCES.find(s => s.v === v) || {}).label)
        .filter(Boolean);

      const lead = await postJSON({
        action: 'lead',
        sourceType: data.sourceType,
        url: data.url,
        pain: data.pain,
        trafficSource: trafficLabels,
        trafficVolume: data.trafficVolume,
        projectType: data.projectType,
        audience: data.audience,
        email: data.email,
        telegram: data.telegram,
        consent: data.consent,
        filesCount: data.files.length,
        website: hp,                       // honeypot: у человека пустой
        elapsedMs: Date.now() - startedAt.current,
      });

      // Скриншоты идут по одному: у Vercel лимит тела ~4.5 МБ.
      if (data.sourceType === 'files' && data.files.length) {
        const total = data.files.length;
        for (let i = 0; i < total; i++) {
          setUploading({ done: i, total });
          const { base64, name } = await compressImage(data.files[i]);
          await postJSON({
            action: 'file',
            lead_id: lead.lead_id,
            name,
            index: i + 1,
            total,
            data: base64,
          });
        }
        setUploading({ done: total, total });
      }

      window.location.href = 'success.html';
    } catch (err) {
      console.error(err);
      setSubmitted(false);
      setUploading(null);
      setSubmitError(
        (err && err.message) ||
        'Не удалось отправить заявку. Проверьте соединение и попробуйте ещё раз.'
      );
    }
  };

  // File handlers.
  // Храним настоящие File-объекты: раньше здесь оставались только {name,size,type},
  // и байты терялись — отправлять на сервер было нечего.
  const onFiles = (incoming) => {
    const arr = Array.from(incoming || []).filter(f => f && f.type.startsWith('image/'));
    set({ files: [...data.files, ...arr].slice(0, 10) });
  };
  const removeFile = (i) => {
    const next = [...data.files]; next.splice(i, 1);
    set({ files: next });
  };

  const progress = ((step + 1) / STEPS.length) * 100;

  return (
    <div className="wizard-shell">
      <aside className="wizard-side">
        <div className="wizard-side-head">
          <h2>Заявка на UX-аудит</h2>
          <p>5 коротких шагов. Поля можно править на этапе сводки. Заполнение займёт 3–4 минуты.</p>
        </div>

        <div>
          <div className="progress-bar"><div className="fill" style={{ width: progress + '%' }} /></div>
          <div className="progress-meta">
            <span>Шаг {step + 1} / {STEPS.length}</span>
            <span>{Math.round(progress)}%</span>
          </div>
        </div>

        <nav className="step-list">
          {STEPS.map((s, i) => (
            <div
              key={s.key}
              className={'step-item ' + (i === step ? 'is-active ' : '') + (i < step ? 'is-done' : '')}
              onClick={() => i <= step && setStep(i)}
              role="button"
            >
              <div className="ring">{i < step ? '✓' : (i+1).toString().padStart(2,'0')}</div>
              <div>
                <div className="title">{s.title}</div>
                <div className="desc">{s.desc}</div>
              </div>
            </div>
          ))}
        </nav>

        <div className="wizard-help">
          <h4>Нужна помощь?</h4>
          <p>Напишите мне в <a href="https://t.me/AntonShappo" target="_blank" rel="noopener">Телеграм</a> — отвечаю в течение рабочего дня.</p>
        </div>
      </aside>

      <main className="wizard-main">
        {step === 0 && <Step1 data={data} set={set} touched={touched} errors={stepErrors} onFiles={onFiles} removeFile={removeFile} />}
        {step === 1 && <Step2 data={data} set={set} touched={touched} errors={stepErrors} />}
        {step === 2 && <Step3 data={data} set={set} touched={touched} errors={stepErrors} />}
        {step === 3 && <Step4 data={data} set={set} touched={touched} errors={stepErrors} />}
        {step === 4 && <Step5 data={data} goTo={setStep} />}

        {submitError && (
          <div className="submit-error" role="alert">
            <p><strong>Заявка не отправилась.</strong> {submitError}</p>
            <p>Если повторится — напишите на <a href="mailto:ask@ux-radar.ru">ask@ux-radar.ru</a> или
              в <a href="https://t.me/AntonShappo" target="_blank" rel="noopener">Телеграм</a>, разберёмся вручную.</p>
          </div>
        )}

        {/* honeypot: скрыт от людей, боты заполняют */}
        <input
          type="text" name="website" tabIndex="-1" autoComplete="off" aria-hidden="true"
          value={hp} onChange={e => setHp(e.target.value)}
          style={{ position: 'absolute', left: '-9999px', width: 1, height: 1, opacity: 0 }}
        />

        <div className="form-foot">
          <span className="save-hint">
            <span className="dot" />
            {uploading ? `Отправляем скриншоты: ${uploading.done} из ${uploading.total}` : saved}
          </span>
          <div className="form-foot-actions">
            {step > 0 && <button className="btn btn-ghost" onClick={prev} disabled={submitted}>← Назад</button>}
            <button className="btn btn-primary" onClick={next} disabled={submitted}>
              {submitted ? 'Отправляем…' : step === STEPS.length - 1 ? 'Отправить заявку' : 'Далее →'}
            </button>
          </div>
        </div>
      </main>
    </div>
  );
}

/* ---------- Step 1: Source ---------- */
function Step1({ data, set, touched, errors, onFiles, removeFile }) {
  const [drag, setDrag] = useState(false);
  const inputRef = useRef(null);

  return (
    <div>
      <header className="step-head">
        <span className="idx">01 / Источник</span>
        <h1>Что будем анализировать?</h1>
        <p className="step-lede">Ссылка на действующую страницу или скриншоты макета. Подходят оба варианта — выбирайте, что доступнее.</p>
      </header>

      <div className="source-tabs" role="tablist">
        <button className={'source-tab ' + (data.sourceType === 'url' ? 'is-active' : '')} onClick={() => set({ sourceType: 'url' })}>
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><path d="M10 14l-2 2a4.5 4.5 0 1 1-2-2l2-2"/><path d="M14 10l2-2a4.5 4.5 0 1 1 2 2l-2 2"/><path d="M9 15l6-6"/></svg>
          URL страницы
        </button>
        <button className={'source-tab ' + (data.sourceType === 'files' ? 'is-active' : '')} onClick={() => set({ sourceType: 'files' })}>
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 17l5-5 3 3 4-4 6 6"/></svg>
          Скриншоты
        </button>
      </div>

      {data.sourceType === 'url' ? (
        <div className="field">
          <label className="label" htmlFor="url">Ссылка на страницу <span className="field-required">*</span></label>
          <input
            id="url"
            className={'input ' + (touched.url && errors.url ? 'is-invalid' : (data.url && !errors.url ? 'is-valid' : ''))}
            placeholder="https://example.ru/pricing"
            value={data.url}
            onChange={e => set({ url: e.target.value })}
            style={{ fontFamily: 'var(--font-mono)', fontSize: 14 }}
          />
          {touched.url && errors.url
            ? <p className="error-msg">{errors.url}</p>
            : <p className="helper">Подойдёт любая публично доступная страница: лендинг, страница тарифов, продуктовая, страница заявки. Если страница закрыта авторизацией — переключитесь на «Скриншоты».</p>}
        </div>
      ) : (
        <div className="field">
          <label className="label">Скриншоты страницы или макета <span className="field-required">*</span></label>
          <div
            className={'dropzone ' + (drag ? 'is-drag' : '')}
            onClick={() => inputRef.current && inputRef.current.click()}
            onDragOver={e => { e.preventDefault(); setDrag(true); }}
            onDragLeave={() => setDrag(false)}
            onDrop={e => { e.preventDefault(); setDrag(false); onFiles(e.dataTransfer.files); }}
          >
            <svg className="dropzone-ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
              <path d="M12 16V4M12 4l-4 4M12 4l4 4M4 20h16"/>
            </svg>
            <strong>Перетащите файлы сюда</strong>
            <small>PNG, JPG или WEBP · до 10 файлов · до 8 МБ каждый</small>
            <span className="pick">или выберите вручную</span>
            <input ref={inputRef} type="file" multiple accept="image/png,image/jpeg,image/webp"
              onChange={e => onFiles(e.target.files)} style={{ display:'none' }} />
          </div>
          {touched.files && errors.files && <p className="error-msg">{errors.files}</p>}
          <p className="helper">Желательно прислать и десктоп, и мобильную версии. Подходит экспорт из Figma. Порядок файлов сохраняется — назовите их так, как пользователь движется по сайту.</p>

          {data.files.length > 0 && (
            <div className="files-list">
              {data.files.map((f, i) => (
                <div className="file-pill" key={i}>
                  <svg className="ic" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
                    <rect x="3" y="5" width="18" height="14" rx="2"/>
                    <path d="M3 17l5-5 3 3 4-4 6 6"/>
                  </svg>
                  <span className="name">{f.name}</span>
                  <span className="size">{formatBytes(f.size)}</span>
                  <button className="rm" onClick={() => removeFile(i)} aria-label="Удалить">×</button>
                </div>
              ))}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

/* ---------- Step 2: Pain ---------- */
function Step2({ data, set, touched, errors }) {
  const len = data.pain.length;
  const min = 12, max = 600;
  return (
    <div>
      <header className="step-head">
        <span className="idx">02 / Задача</span>
        <h1>Что вас не устраивает в текущем виде?</h1>
        <p className="step-lede">Чем конкретнее опишете — тем точнее аудит. Можно начать с одной из подсказок ниже и дополнить своими наблюдениями.</p>
      </header>

      <div className="field" style={{ maxWidth: 720 }}>
        <label className="label" htmlFor="pain">Опишите задачу или сомнение <span className="field-required">*</span></label>
        <textarea
          id="pain"
          className={'textarea ' + (touched.pain && errors.pain ? 'is-invalid' : '')}
          placeholder="Например: трафик с контекста стабильный, но конверсия в заявку упала после обновления hero. Подозреваем, что новая формулировка оффера не считывается на мобильном."
          value={data.pain}
          onChange={e => set({ pain: e.target.value.slice(0, max) })}
          rows={6}
        />
        <div style={{ display:'flex', justifyContent:'space-between', gap: 12, flexWrap:'wrap' }}>
          {touched.pain && errors.pain
            ? <p className="error-msg" style={{ margin: '6px 0 0' }}>{errors.pain}</p>
            : <p className="helper">Опишите ситуацию в свободной форме. 2–4 предложений обычно достаточно.</p>}
          <span className="counter">{len} / {max}</span>
        </div>

        <div className="hint-chips" style={{ marginTop: 14 }}>
          {PAIN_HINTS.map(h => (
            <button key={h} type="button" className="hint-chip" onClick={() => {
              const sep = data.pain && !data.pain.endsWith(' ') ? ' ' : '';
              set({ pain: (data.pain + sep + h).slice(0, max) });
            }}>+ {h}</button>
          ))}
        </div>
      </div>
    </div>
  );
}

/* ---------- Step 3: Context ---------- */
function Step3({ data, set, touched, errors }) {
  return (
    <div>
      <header className="step-head">
        <span className="idx">03 / Контекст</span>
        <h1>Откуда приходят люди и кто они?</h1>
        <p className="step-lede">Эта информация нужна, чтобы привязать рекомендации к реальному поведению аудитории. Если что-то неизвестно — пропустите.</p>
      </header>

      <div className="field">
        <label className="label">Основные каналы трафика <span className="field-required">*</span> <span className="field-optional">можно выбрать несколько</span></label>
        <div className="radio-grid">
          {TRAFFIC_SOURCES.map(s => {
            const selected = (data.trafficSource || []).includes(s.v);
            const toggle = () => {
              const cur = data.trafficSource || [];
              if (s.v === 'none') { set({ trafficSource: selected ? [] : ['none'] }); return; }
              const next = cur.includes(s.v) ? cur.filter(x => x !== s.v) : [...cur.filter(x => x !== 'none'), s.v];
              set({ trafficSource: next });
            };
            return (
              <label key={s.v} className={'radio-card ' + (selected ? 'is-selected' : '')}>
                <input type="checkbox" name="ts" checked={selected} onChange={toggle} />
                <span className="dot dot--check" />
                <span>
                  <span className="label" style={{ marginBottom: 0 }}>{s.label}</span>
                  <span className="sub">{s.sub}</span>
                </span>
              </label>
            );
          })}
        </div>
        {touched.trafficSource && errors.trafficSource && <p className="error-msg">{errors.trafficSource}</p>}
      </div>

      <div className="field">
        <label className="label">Примерный объём трафика на страницу <span className="field-required">*</span></label>
        <div className="pill-row">
          {TRAFFIC_VOLUMES.map(v => (
            <button key={v} type="button"
              className={'pill-btn ' + (data.trafficVolume === v ? 'is-selected' : '')}
              onClick={() => set({ trafficVolume: v })}>{v}</button>
          ))}
        </div>
        {touched.trafficVolume && errors.trafficVolume && <p className="error-msg">{errors.trafficVolume}</p>}
        <p className="helper">Достаточно порядка — точные цифры не нужны.</p>
      </div>

      <div className="field" style={{ marginTop: 20 }}>
        <label className="label" htmlFor="aud">Кто ваша аудитория? <span className="field-optional">по желанию</span></label>
        <input id="aud" className="input"
          placeholder="например: владельцы малого бизнеса, 30–55, ищут способ автоматизировать учёт"
          value={data.audience} onChange={e => set({ audience: e.target.value })} />
        <p className="helper">Одно-два предложения о ключевом сегменте.</p>
      </div>
    </div>
  );
}

/* ---------- Step 4: Contacts ---------- */
function Step4({ data, set, touched, errors }) {
  return (
    <div>
      <header className="step-head">
        <span className="idx">04 / Контакты</span>
        <h1>Куда прислать отчёт?</h1>
        <p className="step-lede">Email обязателен — на него мы пришлём готовый отчёт. Телеграм поможет быстро уточнить детали, если по заявке возникнут вопросы.</p>
      </header>

      <div className="field-row">
        <div className="field">
          <label className="label" htmlFor="em">Email <span className="field-required">*</span></label>
          <input id="em" type="email" className={'input ' + (touched.email && errors.email ? 'is-invalid' : (data.email && !errors.email ? 'is-valid' : ''))}
            placeholder="you@company.com"
            value={data.email}
            onChange={e => set({ email: e.target.value })} />
          {touched.email && errors.email
            ? <p className="error-msg">{errors.email}</p>
            : <p className="helper">На эту почту пришлём готовый отчёт с аудитом.</p>}
        </div>
        <div className="field">
          <label className="label" htmlFor="tg">Telegram <span className="field-optional">по желанию</span></label>
          <div className={'input input-prefixed ' + (touched.telegram && errors.telegram ? 'is-invalid' : '')}>
            <span className="input-prefix" aria-hidden="true">@</span>
            <input id="tg" className="input-bare"
              placeholder="username"
              autoComplete="off" autoCapitalize="off" spellCheck="false"
              value={data.telegram}
              onChange={e => set({ telegram: e.target.value.replace(/^@+/, '') })} />
          </div>
          {touched.telegram && errors.telegram
            ? <p className="error-msg">{errors.telegram}</p>
            : <p className="helper">Только для уточнений по заявке. В рассылку не добавляем.</p>}
        </div>
      </div>

      <label style={{ display:'flex', gap: 12, alignItems:'flex-start', marginTop: 20, maxWidth: 640, cursor:'pointer' }}>
        <input type="checkbox" checked={data.consent} onChange={e => set({ consent: e.target.checked })}
          style={{ width: 18, height: 18, marginTop: 2, accentColor: '#0A53D8' }} />
        <span style={{ fontSize: 14, color:'var(--ink-2)', lineHeight: 1.5 }}>
          Я согласен на обработку персональных данных в соответствии с <a href="privacy.html" target="_blank" rel="noopener" onClick={e => e.stopPropagation()} style={{borderBottom:'1px solid var(--line-2)', color:'var(--ink)'}}>Политикой конфиденциальности</a> и принимаю условия <a href="oferta.html" target="_blank" rel="noopener" onClick={e => e.stopPropagation()} style={{borderBottom:'1px solid var(--line-2)', color:'var(--ink)'}}>Оферты</a>.
        </span>
      </label>
      {touched.consent && errors.consent && <p className="error-msg" style={{marginTop: 4}}>{errors.consent}</p>}
    </div>
  );
}

/* ---------- Step 5: Review ---------- */
function Step5({ data, goTo }) {
  const sourceLabel = data.sourceType === 'url' ? 'URL страницы' : 'Скриншоты';
  const sourceValue = data.sourceType === 'url'
    ? (data.url || <em>не указано</em>)
    : (data.files.length ? data.files.map(f => f.name).join(', ') : <em>нет файлов</em>);

  const trafficLabel = (data.trafficSource || [])
    .map(v => (TRAFFIC_SOURCES.find(s => s.v === v) || {}).label)
    .filter(Boolean).join(', ');

  return (
    <div>
      <header className="step-head">
        <span className="idx">05 / Сводка</span>
        <h1>Проверьте и отправьте заявку</h1>
        <p className="step-lede">Если что-то нужно изменить — нажмите «Изменить» рядом с нужной строкой. После отправки я свяжусь с вами в течение рабочего дня.</p>
      </header>

      <div className="summary">
        <Row k="Источник"     v={sourceLabel}                              onEdit={() => goTo(0)} />
        <Row k="Данные"       v={sourceValue}                              onEdit={() => goTo(0)} />
        <Row k="Задача"       v={data.pain || <em>не заполнено</em>}        onEdit={() => goTo(1)} />
        <Row k="Канал"        v={trafficLabel || <em>не выбрано</em>}       onEdit={() => goTo(2)} />
        <Row k="Объём"        v={data.trafficVolume || <em>не выбрано</em>} onEdit={() => goTo(2)} />
        <Row k="Аудитория"    v={data.audience || '—'}                      onEdit={() => goTo(2)} />
        <Row k="Email"        v={data.email || <em>не указан</em>}          onEdit={() => goTo(3)} />
        <Row k="Telegram"     v={data.telegram || '—'}                      onEdit={() => goTo(3)} />
      </div>

      <p className="legal">
        Нажимая «Отправить заявку», вы соглашаетесь на обработку персональных данных. Мы не передаём контакты третьим лицам и не добавляем в рассылку без отдельного согласия.
      </p>
    </div>
  );
}

function Row({ k, v, onEdit }) {
  const isEmpty = typeof v !== 'string' && v && v.type === 'em';
  return (
    <div className="summary-row">
      <div className="summary-key">{k}</div>
      <div className={'summary-val ' + (isEmpty ? 'empty' : '')}>{v}</div>
      <button className="summary-edit" onClick={onEdit}>Изменить</button>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById('app'));
root.render(<App />);
