// app.jsx — СНТ «Змеиная горка» voting frontend

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

// ─── Data ─────────────────────────────────────────────────────────────
const OPTIONS = [
  { id: 'za',      label: 'За',         className: 'za',      icon: 'check' },
  { id: 'protiv',  label: 'Против',     className: 'protiv',  icon: 'cross' },
  { id: 'abstain', label: 'Воздержался', className: 'abstain', icon: 'dash'  },
];

// ─── Helpers ──────────────────────────────────────────────────────────
function formatPhone(raw){
  // Russian mask: +7 (XXX) XXX-XX-XX
  const digits = raw.replace(/\D/g, '').replace(/^8/, '7').slice(0, 11);
  const d = digits.startsWith('7') ? digits : ('7' + digits).slice(0, 11);
  if (d.length <= 1) return '+7 ';
  const p1 = d.slice(1, 4);
  const p2 = d.slice(4, 7);
  const p3 = d.slice(7, 9);
  const p4 = d.slice(9, 11);
  let out = '+7';
  if (p1) out += ' (' + p1;
  if (p1 && p1.length === 3) out += ')';
  if (p2) out += ' ' + p2;
  if (p3) out += '-' + p3;
  if (p4) out += '-' + p4;
  return out;
}
function phoneIsValid(formatted){
  return formatted.replace(/\D/g, '').length === 11;
}
function maskPhone(formatted){
  // +7 (905) ***-**-12  — leave country, area, last two
  const digits = formatted.replace(/\D/g, '');
  if (digits.length < 11) return formatted;
  return `+7 (${digits.slice(1,4)}) ···-··-${digits.slice(9,11)}`;
}
function todayLocaleRu(){
  const d = new Date();
  const months = ['января','февраля','марта','апреля','мая','июня','июля','августа','сентября','октября','ноября','декабря'];
  return `${d.getDate()} ${months[d.getMonth()]} ${d.getFullYear()}`;
}

// ─── Icon (inline SVG) ────────────────────────────────────────────────
function Glyph({ kind }){
  const s = { width: 12, height: 12, stroke: 'currentColor', fill: 'none', strokeWidth: 2.4, strokeLinecap: 'round', strokeLinejoin: 'round' };
  if (kind === 'check') return <svg viewBox="0 0 24 24" style={s}><polyline points="4 12 10 18 20 6" /></svg>;
  if (kind === 'cross') return <svg viewBox="0 0 24 24" style={s}><line x1="6" y1="6" x2="18" y2="18" /><line x1="18" y1="6" x2="6" y2="18" /></svg>;
  if (kind === 'dash')  return <svg viewBox="0 0 24 24" style={s}><line x1="5" y1="12" x2="19" y2="12" /></svg>;
  return null;
}

// ─── Topbar ───────────────────────────────────────────────────────────
function Topbar({ poll }){
  return (
    <header className="topbar">
      <div className="topbar-inner">
        <div className="brand">
          <div className="brand-mark" aria-hidden="true">З</div>
          <div className="brand-text">
            <div className="brand-snt">{poll?.title || 'Электронное голосование'}</div>
            <div className="brand-sub">Тестовое голосование</div>
          </div>
        </div>
        <div className="top-meta">
          <span className="pill green">
            <svg width="10" height="10" viewBox="0 0 10 10" aria-hidden="true"><circle cx="5" cy="5" r="3.5" fill="currentColor" /></svg>
            тестовое
          </span>
        </div>
      </div>
    </header>
  );
}

// ─── Hero ─────────────────────────────────────────────────────────────
function Hero({ poll }){
  return (
    <section className="hero">
      <h1>{poll?.title || ''}</h1>
      <p className="lede">{poll?.subtitle || 'Заполните бюллетень и подтвердите номер телефона СМС-кодом. С одного номера можно проголосовать только один раз.'}</p>
    </section>
  );
}

// ─── Progress ─────────────────────────────────────────────────────────
function Progress({ votes, total }){
  const answered = Object.keys(votes).filter(k => votes[k]).length;
  const pct = total > 0 ? Math.round((answered / total) * 100) : 0;
  return (
    <div className="progress-wrap">
      <div className="progress-rail">
        <div className="progress-fill" style={{ '--pct': pct + '%' }} />
      </div>
      <div className="progress-text">
        <span><b>{answered}</b> из <b>{total}</b> вопросов отмечено</span>
        <span>{pct}%</span>
      </div>
    </div>
  );
}

// ─── Question card ────────────────────────────────────────────────────
function QuestionCard({ index, q, value, onChange }){
  return (
    <article className={'q' + (value ? ' answered' : '')} id={'q-' + index}>
      <div className="q-head">
        <div className="q-num">{String(index + 1).padStart(2, '0')}</div>
        <div className="q-body">
          <h3 className="q-title">{q.title}</h3>
          <div className="q-context">{q.context}</div>
        </div>
      </div>
      <div className="opts" role="radiogroup" aria-label={'Вопрос ' + (index + 1)}>
        {OPTIONS.map(o => {
          const selected = value === o.id;
          return (
            <button
              key={o.id}
              type="button"
              role="radio"
              aria-checked={selected}
              className={'opt ' + o.className + (selected ? ' selected' : '')}
              onClick={() => onChange(o.id)}
            >
              <span className="glyph"><Glyph kind={o.icon} /></span>
              {o.label}
            </button>
          );
        })}
      </div>
    </article>
  );
}

// ─── Voter form ───────────────────────────────────────────────────────
function VoterForm({ data, setData, errors }) {
  return (
    <div className="form-card">
      <div className="form-grid">
        <div className="field full">
          <label className="lbl" htmlFor="f-fio">ФИО <span className="hint">полностью</span></label>
          <input id="f-fio" className={'input' + (errors.fio ? ' error' : '')}
            placeholder="Иванов Иван Иванович" value={data.fio}
            onChange={e => setData(d => ({ ...d, fio: e.target.value }))} />
          {errors.fio && <div className="err-msg">{errors.fio}</div>}
        </div>
        <div className="field full">
          <label className="lbl" htmlFor="f-phone">Номер телефона <span className="hint">для СМС-кода</span></label>
          <input id="f-phone" className={'input' + (errors.phone ? ' error' : '')}
            type="tel" inputMode="tel" placeholder="+7 (___) ___-__-__"
            value={data.phone}
            onChange={e => setData(d => ({ ...d, phone: formatPhone(e.target.value) }))} />
          {errors.phone && <div className="err-msg">{errors.phone}</div>}
        </div>
      </div>
    </div>
  );
}

// ─── SMS Modal ────────────────────────────────────────────────────────
function SmsModal({ phone, fio, questions, votes, onClose, onVerified }){
  const [digits, setDigits] = useState(['','','','']);
  const [err, setErr] = useState(false);
  const [errText, setErrText] = useState('Неверный код.');
  const [seconds, setSeconds] = useState(45);
  const refs = useRef([]);

  useEffect(() => {
    refs.current[0]?.focus();
  }, []);
  useEffect(() => {
    if (seconds <= 0) return;
    const t = setTimeout(() => setSeconds(s => s - 1), 1000);
    return () => clearTimeout(t);
  }, [seconds]);

  function setAt(i, val){
    const v = val.replace(/\D/g,'').slice(0,1);
    setErr(false);
    setDigits(d => {
      const next = [...d]; next[i] = v;
      return next;
    });
    if (v && i < 3) refs.current[i+1]?.focus();
  }
  function onKey(i, e){
    if (e.key === 'Backspace' && !digits[i] && i > 0) refs.current[i-1]?.focus();
    if (e.key === 'ArrowLeft' && i > 0) refs.current[i-1]?.focus();
    if (e.key === 'ArrowRight' && i < 3) refs.current[i+1]?.focus();
  }
  function onPaste(e){
    const t = (e.clipboardData.getData('text') || '').replace(/\D/g,'').slice(0,4);
    if (!t) return;
    e.preventDefault();
    const next = ['','','',''];
    t.split('').forEach((c, i) => next[i] = c);
    setDigits(next);
    refs.current[Math.min(t.length, 3)]?.focus();
  }

  const code = digits.join('');

  async function submitCode(c) {
    const answers = questions.map(q => ({ questionId: q.id, choice: votes[q.id] }));
    const r = await fetch('/api/vote/verify', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ phone, code: c, fio, answers }),
    });
    if (r.ok) { onVerified(await r.json()); return; }
    const e = (await r.json().catch(() => ({}))).error;
    setErrText({
      bad_code: 'Неверный код.',
      code_expired: 'Код истёк, запросите новый.',
      already_voted: 'С этого номера уже проголосовали.',
      incomplete: 'Не все вопросы отмечены.',
    }[e] || 'Ошибка проверки.');
    setErr(true);
    setTimeout(() => { setDigits(['', '', '', '']); refs.current[0]?.focus(); setErr(false); }, 800);
  }

  useEffect(() => { if (code.length === 4) submitCode(code); }, [code]);

  async function handleResend() {
    setSeconds(45);
    setDigits(['','','','']);
    refs.current[0]?.focus();
    await fetch('/api/vote/request-code', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ phone, fio }),
    });
  }

  return (
    <div className="scrim" role="dialog" aria-modal="true" aria-labelledby="sms-h">
      <div className="modal">
        <div className="modal-pad">
          <h3 id="sms-h">Подтвердите номер телефона</h3>
          <p className="sub">Мы отправили четырёхзначный код на номер <b>{maskPhone(phone)}</b>. Введите его, чтобы зафиксировать ваш голос.</p>

          <div className={'otp' + (err ? ' error shake' : '')} onPaste={onPaste}>
            {digits.map((d, i) => (
              <input
                key={i}
                ref={el => refs.current[i] = el}
                type="text"
                inputMode="numeric"
                autoComplete="one-time-code"
                maxLength={1}
                value={d}
                onChange={e => setAt(i, e.target.value)}
                onKeyDown={e => onKey(i, e)}
                aria-label={`Цифра ${i+1}`}
              />
            ))}
          </div>

          <div className="resend">
            {seconds > 0
              ? <>Отправить код повторно через <b style={{color:'var(--ink-2)'}}>{seconds} с</b></>
              : <button type="button" onClick={handleResend}>Отправить код повторно</button>}
          </div>
          {err && <div className="err-msg" style={{textAlign:'center'}}>{errText}</div>}
        </div>
        <div className="modal-actions">
          <button className="btn-ghost" type="button" onClick={onClose}>Изменить данные</button>
        </div>
      </div>
    </div>
  );
}

// ─── Success / Ballot preview ─────────────────────────────────────────
function BallotPreview({ data, votes, questions, ballotId, timestamp }){
  return (
    <div className="ballot-wrap">
      <div className="ballot-head">
        <div>
          <h4>Бюллетень для голосования</h4>
          <div style={{fontSize:12, color:'#6b7163'}}>Электронное голосование СНТ «Змеиная горка» · {todayLocaleRu()}</div>
        </div>
        <div className="meta">
          № {ballotId}<br/>
          {timestamp}
        </div>
      </div>

      <table className="ballot-table">
        <thead>
          <tr>
            <th style={{width:'70%'}}>Вопрос повестки</th>
            <th style={{textAlign:'right'}}>Решение</th>
          </tr>
        </thead>
        <tbody>
          {questions.map((q, i) => {
            const v = votes[q.id];
            const opt = OPTIONS.find(o => o.id === v);
            return (
              <tr key={q.id}>
                <td>
                  <div style={{fontWeight:600, fontSize:13.5}}>{i+1}. {q.title}</div>
                </td>
                <td className={'vote ' + (opt ? opt.className : '')}>
                  {opt ? opt.label.toUpperCase() : '—'}
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>

      <div className="stamp" aria-label="клише простой электронной подписи">
        <div className="right">
          <div className="stamp-title">Простая электронная подпись</div>
          <dl>
            <dt>Подписал:</dt><dd>{data.fio || '—'}</dd>
            <dt>Телефон:</dt><dd>{maskPhone(data.phone)}</dd>
            <dt>Время:</dt><dd>{timestamp}</dd>
            <dt>ID бюллетеня:</dt><dd className="stamp-id">{ballotId}</dd>
          </dl>
        </div>
      </div>
    </div>
  );
}

function SuccessModal({ verified, data, questions, votes, poll, onDownload, onClose }){
  const ballotId = verified.ballotCode;
  const timestamp = new Date(verified.createdAt + 'Z').toLocaleString('ru-RU', { dateStyle: 'long', timeStyle: 'short' });

  return (
    <div className="scrim" role="dialog" aria-modal="true">
      <div className="modal wide">
        <div className="modal-pad" style={{textAlign:'center'}}>
          <div className="success-mark" aria-hidden="true">
            <svg viewBox="0 0 24 24"><polyline points="4 13 10 19 20 6" /></svg>
          </div>
          <h3>Голос зафиксирован, спасибо!</h3>
          <p className="sub" style={{margin:'0 auto 6px', maxWidth:'46ch'}}>
            Бюллетень подписан простой электронной подписью и принят счётной комиссией.
            Сохраните копию — она пригодится, если потребуется подтвердить ваш голос.
          </p>
        </div>

        <BallotPreview data={data} votes={votes} questions={questions} ballotId={ballotId} timestamp={timestamp} />

        <div className="success-actions">
          <button className="btn-primary" type="button" onClick={onDownload}>
            <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4v12" /><polyline points="6 12 12 18 18 12" /><line x1="4" y1="21" x2="20" y2="21" /></svg>
            Скачать бюллетень (PDF)
          </button>
          <button className="btn-ghost" type="button" onClick={onClose}>Закрыть</button>
        </div>
      </div>
    </div>
  );
}

// ─── Main App ─────────────────────────────────────────────────────────
function App(){
  const [poll, setPoll] = useState(null);
  const [questions, setQuestions] = useState([]);
  const [loadErr, setLoadErr] = useState(null);
  useEffect(() => {
    fetch('/api/poll').then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(d => { setPoll(d.poll); setQuestions(d.questions); })
      .catch(() => setLoadErr('Голосование сейчас закрыто или ещё не начато.'));
  }, []);

  const [votes, setVotes] = useState({});  // { [questionId]: 'za'|'protiv'|'abstain' }
  const [data, setData] = useState({ fio: '', phone: '+7 ' });
  const [screen, setScreen] = useState('ballot'); // 'ballot' | 'sms' | 'success'
  const [errors, setErrors] = useState({});
  const [sending, setSending] = useState(false);
  const [submitError, setSubmitError] = useState(null);
  const [verified, setVerified] = useState(null);
  const submitBtnRef = useRef(null);

  const allVoted = questions.length > 0 && questions.every(q => votes[q.id]);
  const formReady = data.fio.trim().split(/\s+/).length >= 2 && phoneIsValid(data.phone);

  function validate(){
    const e = {};
    if (data.fio.trim().split(/\s+/).length < 2) e.fio = 'Укажите фамилию и имя как минимум';
    if (!phoneIsValid(data.phone)) e.phone = 'Введите номер полностью';
    setErrors(e);
    return Object.keys(e).length === 0;
  }

  async function onSubmit() {
    if (!allVoted) {
      const first = questions.find(q => !votes[q.id]);
      if (first) document.getElementById('q-' + questions.indexOf(first))?.scrollIntoView({ behavior: 'smooth', block: 'start' });
      return;
    }
    if (!validate()) {
      document.querySelector('.input.error')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
      return;
    }
    setSubmitError(null); setSending(true);
    const r = await fetch('/api/vote/request-code', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ phone: data.phone, fio: data.fio }),
    });
    setSending(false);
    if (r.ok) { setScreen('sms'); return; }
    const err = (await r.json().catch(() => ({}))).error;
    const msg = {
      already_voted: 'С этого номера уже проголосовали.',
      rate_limited: 'Слишком часто. Попробуйте через минуту.',
      sms_failed: 'Не удалось отправить СМС. Попробуйте ещё раз.',
      no_active_poll: 'Голосование сейчас закрыто.',
      bad_phone: 'Проверьте номер телефона.',
      bad_fio: 'Проверьте ФИО (должно быть минимум два слова).',
    }[err] || 'Ошибка. Попробуйте ещё раз.';
    setSubmitError(msg);
  }

  function downloadBallot(ballotId, timestamp){
    const win = window.open('', '_blank');
    if (!win) return;
    const rows = questions.map((q, i) => {
      const opt = OPTIONS.find(o => o.id === votes[q.id]);
      const color = opt?.id === 'za' ? '#2d5a3d' : opt?.id === 'protiv' ? '#b8412a' : '#8e6a1d';
      return `<tr>
        <td style="padding:10px 8px;border-bottom:1px solid #d6d2c4;vertical-align:top;font-size:13px;line-height:1.5">${i+1}. ${q.title}</td>
        <td style="padding:10px 8px;border-bottom:1px solid #d6d2c4;font-weight:600;color:${color};text-align:right;white-space:nowrap;font-size:13px">${opt?.label.toUpperCase() || '—'}</td>
      </tr>`;
    }).join('');
    const html = `<!doctype html><html lang="ru"><head><meta charset="utf-8"><title>Бюллетень ${ballotId}</title>
      <style>
        @page { size: A4; margin: 22mm 18mm; }
        body{ font-family: 'Source Serif 4', Georgia, serif; color:#1d211a; margin:0; padding:0; background:#fff; }
        h1{ font-size:20px; margin:0 0 4px; font-weight:600; }
        .sub{ font-size:12px; color:#5b6055; margin-bottom: 14px; }
        table{ width:100%; border-collapse: collapse; margin: 12px 0 24px; }
        th{ font-family: 'Manrope', sans-serif; font-size: 10px; text-transform: uppercase; letter-spacing: .1em; color:#5b6055; padding: 8px; border-bottom: 1.5px solid #1d211a; text-align: left; }
        .stamp{ display:inline-block; border:2px solid #2d5a3d; border-radius:8px; padding:12px 16px; transform:rotate(-1.2deg); margin-top: 10px; font-family:'JetBrains Mono', monospace; color:#2d5a3d; background:rgba(45,90,61,.04); }
        .stamp-title{ font-size:9px; letter-spacing:.15em; text-transform:uppercase; margin-bottom:6px; opacity:.85; }
        .stamp dl{ margin:0; display:grid; grid-template-columns:auto 1fr; column-gap:14px; row-gap:3px; font-size:11px; }
        .stamp dt{ color: rgba(45,90,61,.7); }
        .stamp dd{ margin:0; font-weight:600; }
        .meta-bar{ display:flex; justify-content:space-between; font-size:11px; font-family:'JetBrains Mono', monospace; color:#5b6055; border-top:1px solid #d6d2c4; padding-top: 10px; }
        .doc-head{ display:flex; justify-content:space-between; align-items:flex-end; border-bottom:1px solid #1d211a; padding-bottom: 10px; margin-bottom: 16px; }
        .doc-head .right{ text-align:right; font-size:11px; font-family:'JetBrains Mono', monospace; }
      </style>
      </head><body>
      <div class="doc-head">
        <div>
          <h1>Бюллетень для голосования</h1>
          <div class="sub">${poll?.title || 'Электронное голосование'}<br/>${timestamp}</div>
        </div>
        <div class="right">№ ${ballotId}<br/>${timestamp}</div>
      </div>
      <table>
        <thead><tr><th style="width:75%">Вопрос повестки</th><th style="text-align:right">Решение</th></tr></thead>
        <tbody>${rows}</tbody>
      </table>
      <div style="font-size:12px; line-height:1.55; margin-bottom: 14px;">
        <b>Участник голосования:</b> ${data.fio}<br/>
        <b>Контактный номер:</b> ${maskPhone(data.phone)}
      </div>
      <div class="stamp">
        <div class="stamp-title">Простая электронная подпись</div>
        <dl>
          <dt>Подписал:</dt><dd>${data.fio}</dd>
          <dt>Телефон:</dt><dd>${maskPhone(data.phone)}</dd>
          <dt>Время подписи:</dt><dd>${timestamp}</dd>
          <dt>ID бюллетеня:</dt><dd>${ballotId}</dd>
        </dl>
      </div>
      <div style="margin-top: 60px;" class="meta-bar">
        <span>Сформировано системой test.zmgorka.ru</span>
        <span>Стр. 1 из 1</span>
      </div>
      <script>setTimeout(()=>window.print(),250);</script>
      </body></html>`;
    win.document.write(html);
    win.document.close();
  }

  if (!poll && !loadErr) {
    return <div style={{padding:40, textAlign:'center', color:'var(--ink-3)'}}>Загрузка…</div>;
  }
  if (loadErr) {
    return <div style={{padding:40, textAlign:'center', color:'var(--ink-3)'}}>{loadErr}</div>;
  }

  return (
    <div className="app">
      <Topbar poll={poll} />
      <Hero poll={poll} />
      <Progress votes={votes} total={questions.length} />

      <main>
        <div className="section-title">Повестка собрания</div>
        <div className="q-list">
          {questions.map((q, i) => (
            <QuestionCard
              key={q.id}
              index={i}
              q={q}
              value={votes[q.id]}
              onChange={v => setVotes(prev => ({ ...prev, [q.id]: v }))}
            />
          ))}
        </div>

        <div className="section-title">Данные голосующего</div>
        <VoterForm data={data} setData={setData} errors={errors} />

        <div className="submit-bar">
          <div className="submit-meta">
            Нажимая «Отправить», вы подтверждаете согласие на обработку персональных данных. Голос можно изменить только до получения СМС-кода.
            {!allVoted && <><br/><span style={{color:'var(--terra-2)', fontWeight:600}}>Отметьте ответ на все {questions.length} вопросов, чтобы продолжить.</span></>}
            {submitError && <div className="err-msg" style={{marginTop:6}}>{submitError}</div>}
          </div>
          <button
            ref={submitBtnRef}
            className="btn-primary"
            type="button"
            onClick={onSubmit}
            disabled={!allVoted || !formReady || sending}
          >
            {sending ? 'Отправка…' : 'Отправить бюллетень'}
            <svg viewBox="0 0 24 24" aria-hidden="true"><line x1="5" y1="12" x2="19" y2="12" /><polyline points="13 6 19 12 13 18" /></svg>
          </button>
        </div>
      </main>

      <footer className="foot">
        Если у вас возникли вопросы — обращайтесь к правлению товарищества.<br/><br/>
        © 2026 СНТ «Змеиная горка» · Тестовый сервис электронного голосования
      </footer>

      {screen === 'sms' && (
        <SmsModal
          phone={data.phone} fio={data.fio} questions={questions} votes={votes}
          onClose={() => setScreen('ballot')}
          onVerified={(v) => { setVerified(v); setScreen('success'); }}
        />
      )}
      {screen === 'success' && verified && (
        <SuccessModal
          verified={verified} data={data} questions={questions} votes={votes} poll={poll}
          onDownload={() => downloadBallot(verified.ballotCode, new Date(verified.createdAt + 'Z').toLocaleString('ru-RU', { dateStyle: 'long', timeStyle: 'short' }))}
          onClose={() => setScreen('ballot')}
        />
      )}
    </div>
  );
}

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