// Public participant survey — mobile frame

function computeVisibleQuestions(questions, answers) {
  const skipped = new Set();
  for (let i = 0; i < questions.length; i++) {
    const q = questions[i];
    if (skipped.has(q.id)) continue;
    const ans = answers[q.id];
    if (ans === undefined || ans === '' || ans === null) continue;
    const map = q.skipMap || {};
    const target = map[ans];
    if (!target) continue;
    if (target === '__end__') {
      for (let j = i + 1; j < questions.length; j++) skipped.add(questions[j].id);
      break;
    } else {
      const tIdx = questions.findIndex(x => x.id === target);
      if (tIdx > i) {
        for (let j = i + 1; j < tIdx; j++) skipped.add(questions[j].id);
      }
    }
  }
  return questions.filter(q => !skipped.has(q.id));
}

const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;

function PublicSurvey({ survey, onSubmit, submitted, enteredDraw }) {
  const [answers, setAnswers] = React.useState({});
  const [prizeEmail, setPrizeEmail] = React.useState('');
  const [prizeErr, setPrizeErr] = React.useState(false);
  const [gated, setGated] = React.useState((survey.access || 'gated') === 'gated');
  const [errors, setErrors] = React.useState({});

  React.useEffect(() => {
    setAnswers({}); setErrors({});
    setGated((survey.access || 'gated') === 'gated');
  }, [survey.id]);

  const setA = (id, v) => setAnswers(a => ({ ...a, [id]: v }));

  const visible = React.useMemo(
    () => computeVisibleQuestions(survey.questions, answers),
    [survey.questions, answers]
  );

  const isAnswered = (q, v) => {
    if (q.type === 'vibe') return v && typeof v === 'object' && Object.keys(v).length > 0;
    if (Array.isArray(v)) return v.length > 0;
    return v !== undefined && v !== '' && v !== null;
  };

  const progress = React.useMemo(() => {
    const req = visible.filter(q => q.required);
    if (req.length === 0) {
      const done = visible.filter(q => isAnswered(q, answers[q.id])).length;
      return Math.round((done / Math.max(1, visible.length)) * 100);
    }
    const done = req.filter(q => isAnswered(q, answers[q.id])).length;
    return Math.round((done / req.length) * 100);
  }, [answers, visible]);

  const handleSubmit = () => {
    const errs = {};
    visible.forEach(q => {
      if (q.required && !isAnswered(q, answers[q.id])) {
        errs[q.id] = 'Required';
      }
    });
    if (Object.keys(errs).length > 0) {
      setErrors(errs);
      const first = document.getElementById('q-' + Object.keys(errs)[0]);
      if (first) first.scrollIntoView({ block: 'center', behavior: 'smooth' });
      return;
    }
    const email = survey.prizeDraw ? prizeEmail.trim() : '';
    if (email && !EMAIL_RE.test(email)) {
      setPrizeErr(true);
      const box = document.getElementById('prize-draw');
      if (box) box.scrollIntoView({ block: 'center', behavior: 'smooth' });
      return;
    }
    // Only submit answers for questions that ended up visible.
    const visibleIds = new Set(visible.map(q => q.id));
    const cleanAnswers = Object.fromEntries(
      Object.entries(answers).filter(([k]) => visibleIds.has(k))
    );
    onSubmit(cleanAnswers, email);
  };

  const lang = survey.lang || 'en';
  const t = (key, vars) => tr(lang, key, vars);

  if (submitted) {
    return <LangContext.Provider value={lang}><PublicThanks survey={survey} enteredDraw={enteredDraw} /></LangContext.Provider>;
  }

  if (gated) {
    return <LangContext.Provider value={lang}><AgeGate onPass={() => setGated(false)} /></LangContext.Provider>;
  }

  return (
    <LangContext.Provider value={lang}>
    <div style={{ minHeight: '100vh', background: 'var(--paper)', color: 'var(--ink)', position: 'relative' }}>
      {/* sticky top */}
      <div style={{
        position: 'sticky', top: 0, zIndex: 8, background: 'var(--paper)',
        borderBottom: '1px solid var(--line)',
      }}>
        <div style={{ padding: '14px 20px', display: 'flex', alignItems: 'center', gap: 12 }}>
          <Wordmark size={22}/>
          <div style={{ flex: 1 }}/>
          <span style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--muted)', letterSpacing: '0.14em' }}>
            {progress}%
          </span>
        </div>
        <div style={{ height: 2, background: 'var(--paper-3)' }}>
          <div style={{ height: '100%', width: progress + '%', background: 'var(--red)', transition: 'width .3s' }}/>
        </div>
      </div>

      <div style={{ padding: '36px 20px 24px', borderBottom: '1px solid var(--line)' }}>
        <Eyebrow>{survey.anonymous ? t('eyebrowAnon') : t('eyebrowSurvey')}</Eyebrow>
        <h1 style={{
          fontFamily: 'var(--font-display)', fontWeight: 400, fontSize: 34,
          lineHeight: 1.1, margin: '16px 0 14px',
        }}>{survey.title}</h1>
        <div style={{ fontSize: 13, color: 'var(--muted)', lineHeight: 1.55 }}>
          {survey.questions.length} {survey.questions.length === 1 ? t('question') : t('questions')} · {surveyDurationLabel(survey)}.{/* shown count is total; actual flow may be shorter via skip logic */}
          {survey.anonymous && ' ' + t('anonNote')} {t('leaveAnyTime')}
        </div>
        {survey.intro && (
          <div style={{ marginTop: 18, fontSize: 14, color: 'var(--ink)', lineHeight: 1.6, whiteSpace: 'pre-wrap' }}>
            {survey.intro}
          </div>
        )}
      </div>

      <div style={{ padding: '8px 20px 40px' }}>
        {visible.map((q, i) => (
          <PublicQuestion
            key={q.id} q={q} index={i + 1}
            total={visible.length}
            value={answers[q.id]} setValue={(v) => { setA(q.id, v); setErrors(e => ({ ...e, [q.id]: undefined })); }}
            error={errors[q.id]}
          />
        ))}

        <div style={{ paddingTop: 24, borderTop: '1px solid var(--line)', marginTop: 16 }}>
          {survey.prizeDraw && (
            <PrizeDraw
              pd={survey.prizeDraw} value={prizeEmail} error={prizeErr}
              onChange={v => { setPrizeEmail(v); setPrizeErr(false); }}
            />
          )}
          <button className="btn btn-red" onClick={handleSubmit} style={{ width: '100%', justifyContent: 'center', padding: '16px' }}>
            {survey.anonymous ? t('submitAnon') : t('submit')} {Icon.arrow(14)}
          </button>
          <div style={{ marginTop: 14, fontSize: 11, color: 'var(--muted)', textAlign: 'center', lineHeight: 1.6 }}>
            {t('confirm18')}
            {survey.anonymous && <><br/>{t('storesAnswers')}</>}
          </div>
        </div>
      </div>
    </div>
    </LangContext.Provider>
  );
}

function AgeGate({ onPass }) {
  const t = useT();
  return (
    <div style={{ minHeight: '100vh', background: 'var(--ink)', color: 'var(--paper)', display: 'flex', flexDirection: 'column' }}>
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: '40px 28px' }}>
        <div style={{ marginBottom: 40 }}>
          <Wordmark size={20} color="var(--paper)"/>
        </div>
        <Eyebrow style={{ color: 'rgba(246,243,238,0.5)' }}>{t('gateEyebrow')}</Eyebrow>
        <h1 style={{ fontFamily: 'var(--font-display)', fontWeight: 400, fontSize: 48, lineHeight: 1.05, margin: '16px 0 20px' }}>
          {t('gateQ1')}<em style={{ color: 'var(--red)' }}>{t('gateEm')}</em>{t('gateQ2')}
        </h1>
        <p style={{ fontSize: 14, color: 'rgba(246,243,238,0.7)', lineHeight: 1.6, marginBottom: 40 }}>
          {t('gateBody')}
        </p>

        <button className="btn btn-red" onClick={onPass} style={{ justifyContent: 'center', padding: 16, fontSize: 14 }}>
          {t('gateYes')} {Icon.arrow(14)}
        </button>
        <a href="https://erobella.com" className="btn" style={{
          justifyContent: 'center', padding: 16, marginTop: 10, textDecoration: 'none',
          background: 'transparent', color: 'var(--paper)', border: '1px solid rgba(246,243,238,0.2)',
        }}>
          {t('gateNo')}
        </a>

        <div style={{ marginTop: 60, fontSize: 11, color: 'rgba(246,243,238,0.4)', fontFamily: 'var(--font-mono)', letterSpacing: '0.1em' }}>
          §2257 · GDPR · EROBELLA B.V.
        </div>
      </div>
    </div>
  );
}

// Optional prize-draw email, shown above the submit button. Not a question:
// it isn't numbered, doesn't count towards progress and is stored apart from answers.
function PrizeDraw({ pd, value, onChange, error }) {
  const t = useT();
  return (
    <div id="prize-draw" style={{
      border: '1px solid ' + (error ? 'var(--red)' : 'var(--line)'), background: 'var(--paper-2)',
      borderRadius: 2, padding: '22px 20px 20px', marginBottom: 20,
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
        <Asterisk size={13}/>
        <Eyebrow style={{ color: 'var(--red)' }}>{t('prizeEyebrow')}</Eyebrow>
      </div>
      <h3 style={{
        fontFamily: 'var(--font-display)', fontWeight: 400, fontSize: 26, lineHeight: 1.15,
        margin: '12px 0 8px', color: 'var(--ink)',
      }}>{pd.title}</h3>
      {pd.body && <div style={{ fontSize: 13, color: 'var(--muted)', lineHeight: 1.55, marginBottom: 14 }}>{pd.body}</div>}
      <input
        className="input"
        type="email"
        inputMode="email"
        autoComplete="email"
        autoCapitalize="off"
        spellCheck={false}
        placeholder={t('emailPlaceholder')}
        aria-label={t('emailAria')}
        value={value}
        onChange={e => onChange(e.target.value)}
        style={{ background: 'var(--paper)', fontSize: 16, borderColor: error ? 'var(--red)' : undefined }}
      />
      {error && (
        <div style={{ marginTop: 8, fontSize: 11, color: 'var(--red)', display: 'flex', alignItems: 'center', gap: 6 }}>
          <span className="dot dot-red"/> {t('emailBad')}
        </div>
      )}
      {pd.terms && <div style={{ marginTop: 12, fontSize: 11, color: 'var(--muted)', lineHeight: 1.55 }}>{pd.terms}</div>}
    </div>
  );
}

function PublicThanks({ survey, enteredDraw }) {
  const t = useT();
  const ty = survey.thankYou || { title: t('thanksTitle'), body: '', cta: '' };
  return (
    <div style={{ minHeight: '100vh', background: 'var(--paper)', display: 'flex', flexDirection: 'column' }}>
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', padding: '48px 28px', textAlign: 'center' }}>
        <div style={{ display: 'flex', justifyContent: 'center', marginBottom: 16 }}>
          <Wordmark size={22}/>
        </div>
        <div style={{ flex: 1 }}/>
        <div style={{ marginBottom: 28 }}>
          <Asterisk size={32}/>
        </div>
        <Eyebrow>{t('recorded')}</Eyebrow>
        <h1 style={{ fontFamily: 'var(--font-display)', fontStyle: 'italic', fontWeight: 400, fontSize: 56, lineHeight: 1.02, margin: '16px 0 20px' }}>
          {ty.title}
        </h1>
        <p style={{ fontSize: 14, color: 'var(--muted)', lineHeight: 1.6, maxWidth: 320, margin: '0 auto 32px' }}>
          {ty.body}
          {enteredDraw && <><br/><br/>{t('inDraw')}</>}
        </p>

        {ty.cta && (
          ty.ctaUrl ? (
            <a href={ty.ctaUrl} target="_blank" rel="noopener noreferrer" style={{
              fontFamily: 'var(--font-mono)', fontSize: 11, letterSpacing: '0.16em',
              textTransform: 'uppercase', color: 'var(--red)', textDecoration: 'none',
              display: 'inline-flex', alignItems: 'center', gap: 8, justifyContent: 'center',
            }}>
              {ty.cta} {Icon.arrow(12)}
            </a>
          ) : (
            <div style={{
              fontFamily: 'var(--font-mono)', fontSize: 11, letterSpacing: '0.16em',
              textTransform: 'uppercase', color: 'var(--red)',
            }}>
              {ty.cta}
            </div>
          )
        )}
        <div style={{ flex: 1 }}/>
      </div>
    </div>
  );
}

function PublicQuestion({ q, index, total, value, setValue, error }) {
  const t = useT();
  return (
    <div id={'q-' + q.id} style={{ padding: '28px 0', borderBottom: '1px solid var(--line)' }}>
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 10, marginBottom: 10 }}>
        <span style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--muted)', letterSpacing: '0.14em' }}>
          {t('qLabel')} {String(index).padStart(2, '0')} / {String(total).padStart(2, '0')}
        </span>
        {q.required && <span style={{ color: 'var(--red)', fontSize: 12 }}>{t('required')}</span>}
      </div>
      <h3 style={{
        fontFamily: 'var(--font-display)', fontWeight: 400, fontSize: 22, lineHeight: 1.25,
        margin: '0 0 6px', color: 'var(--ink)',
      }}>
        {q.title}
      </h3>
      {q.help && <div style={{ fontSize: 12, color: 'var(--muted)', marginBottom: 14, lineHeight: 1.5 }}>{q.help}</div>}

      <div style={{ marginTop: 14 }}>
        <PublicInput q={q} value={value} setValue={setValue} />
      </div>

      {error && (
        <div style={{ marginTop: 10, fontSize: 11, color: 'var(--red)', display: 'flex', alignItems: 'center', gap: 6 }}>
          <span className="dot dot-red"/> {t('requiredError')}
        </div>
      )}
    </div>
  );
}

function PublicInput({ q, value, setValue }) {
  const t = useT();
  if (q.type === 'short') {
    return <input className="input" value={value || ''} onChange={e => setValue(e.target.value)} placeholder={t('typeAnswer')}/>;
  }
  if (q.type === 'long') {
    return <textarea className="input" rows={4} value={value || ''} onChange={e => setValue(e.target.value)} placeholder={t('typeAnswer')} style={{ resize: 'vertical', fontFamily: 'var(--font-ui)' }}/>;
  }
  if (q.type === 'single') {
    const isOther = typeof value === 'string' && value.startsWith('Other:');
    const otherText = isOther ? value.slice(6).trimStart() : '';
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
        {q.options.map(o => {
          const sel = value === o;
          return (
            <button key={o} onClick={() => setValue(o)} style={{
              textAlign: 'left', cursor: 'pointer',
              border: '1px solid ' + (sel ? 'var(--ink)' : 'var(--line)'),
              background: sel ? 'var(--paper-2)' : 'transparent',
              padding: '14px 16px', borderRadius: 2,
              display: 'flex', alignItems: 'center', gap: 12,
              fontFamily: 'var(--font-ui)', fontSize: 15, color: 'var(--ink)',
            }}>
              <span style={{
                width: 18, height: 18, borderRadius: '50%',
                border: '1.5px solid ' + (sel ? 'var(--ink)' : 'var(--line-2)'),
                position: 'relative', flexShrink: 0,
              }}>
                {sel && <span style={{ position: 'absolute', inset: 3, borderRadius: '50%', background: 'var(--red)' }}/>}
              </span>
              {o}
            </button>
          );
        })}
        {q.allowOther && (
          <div style={{
            border: '1px solid ' + (isOther ? 'var(--ink)' : 'var(--line)'),
            background: isOther ? 'var(--paper-2)' : 'transparent',
            padding: '14px 16px', borderRadius: 2,
            display: 'flex', alignItems: 'center', gap: 12,
          }}>
            <span
              onClick={() => setValue('Other: ')}
              style={{
                width: 18, height: 18, borderRadius: '50%', cursor: 'pointer',
                border: '1.5px solid ' + (isOther ? 'var(--ink)' : 'var(--line-2)'),
                position: 'relative', flexShrink: 0,
              }}>
              {isOther && <span style={{ position: 'absolute', inset: 3, borderRadius: '50%', background: 'var(--red)' }}/>}
            </span>
            <input
              type="text"
              placeholder={t('otherSpecify')}
              value={otherText}
              onFocus={() => { if (!isOther) setValue('Other: '); }}
              onChange={e => setValue('Other: ' + e.target.value)}
              style={{
                flex: 1, border: 0, outline: 'none', background: 'transparent',
                fontFamily: 'var(--font-ui)', fontSize: 15, color: 'var(--ink)',
              }}
            />
          </div>
        )}
      </div>
    );
  }
  if (q.type === 'multi') {
    const arr = Array.isArray(value) ? value : [];
    const toggle = (o) => setValue(arr.includes(o) ? arr.filter(x => x !== o) : [...arr, o]);
    const otherIdx = arr.findIndex(x => typeof x === 'string' && x.startsWith('Other:'));
    const otherSel = otherIdx !== -1;
    const otherText = otherSel ? arr[otherIdx].slice(6).trimStart() : '';
    const setOtherText = (t) => {
      const next = arr.filter((_, i) => i !== otherIdx);
      next.push('Other: ' + t);
      setValue(next);
    };
    const toggleOther = () => {
      if (otherSel) setValue(arr.filter((_, i) => i !== otherIdx));
      else setValue([...arr, 'Other: ']);
    };
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
        {q.options.map(o => {
          const sel = arr.includes(o);
          return (
            <button key={o} onClick={() => toggle(o)} style={{
              textAlign: 'left', cursor: 'pointer',
              border: '1px solid ' + (sel ? 'var(--ink)' : 'var(--line)'),
              background: sel ? 'var(--paper-2)' : 'transparent',
              padding: '14px 16px', borderRadius: 2,
              display: 'flex', alignItems: 'center', gap: 12,
              fontFamily: 'var(--font-ui)', fontSize: 15, color: 'var(--ink)',
            }}>
              <span style={{
                width: 18, height: 18, borderRadius: 2,
                border: '1.5px solid ' + (sel ? 'var(--ink)' : 'var(--line-2)'),
                background: sel ? 'var(--ink)' : 'transparent',
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                color: 'var(--paper)', flexShrink: 0,
              }}>
                {sel && Icon.check(10)}
              </span>
              {o}
            </button>
          );
        })}
        {q.allowOther && (
          <div style={{
            border: '1px solid ' + (otherSel ? 'var(--ink)' : 'var(--line)'),
            background: otherSel ? 'var(--paper-2)' : 'transparent',
            padding: '14px 16px', borderRadius: 2,
            display: 'flex', alignItems: 'center', gap: 12,
          }}>
            <span
              onClick={toggleOther}
              style={{
                width: 18, height: 18, borderRadius: 2, cursor: 'pointer',
                border: '1.5px solid ' + (otherSel ? 'var(--ink)' : 'var(--line-2)'),
                background: otherSel ? 'var(--ink)' : 'transparent',
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                color: 'var(--paper)', flexShrink: 0,
              }}>
              {otherSel && Icon.check(10)}
            </span>
            <input
              type="text"
              placeholder={t('otherSpecify')}
              value={otherText}
              onFocus={() => { if (!otherSel) setValue([...arr, 'Other: ']); }}
              onChange={e => setOtherText(e.target.value)}
              style={{
                flex: 1, border: 0, outline: 'none', background: 'transparent',
                fontFamily: 'var(--font-ui)', fontSize: 15, color: 'var(--ink)',
              }}
            />
          </div>
        )}
      </div>
    );
  }
  if (q.type === 'dropdown') {
    const isOther = typeof value === 'string' && value.startsWith('Other:');
    const otherText = isOther ? value.slice(6).trimStart() : '';
    const selectValue = isOther ? '__other__' : (value || '');
    return (
      <div>
        <select
          value={selectValue}
          onChange={e => {
            if (e.target.value === '__other__') setValue('Other: ');
            else setValue(e.target.value);
          }}
          className="input"
          style={{
            appearance: 'none', backgroundImage: 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'14\' height=\'14\' viewBox=\'0 0 24 24\' fill=\'none\' stroke=\'%230b0b0c\' stroke-width=\'1.5\'><path d=\'M6 9l6 6 6-6\'/></svg>")',
            backgroundRepeat: 'no-repeat', backgroundPosition: 'right 14px center', paddingRight: 40,
          }}
        >
          <option value="" disabled>{t('chooseOne')}</option>
          {q.options.map(o => <option key={o} value={o}>{o}</option>)}
          {q.allowOther && <option value="__other__">{t('otherDots')}</option>}
        </select>
        {isOther && (
          <input
            className="input"
            style={{ marginTop: 8 }}
            type="text"
            placeholder={t('pleaseSpecify')}
            value={otherText}
            onChange={e => setValue('Other: ' + e.target.value)}
          />
        )}
      </div>
    );
  }
  if (q.type === 'dropdown-search') {
    return <PublicDropdownSearch q={q} value={value} setValue={setValue} />;
  }
  if (q.type === 'vibe') {
    return <PublicVibe q={q} value={value} setValue={setValue} />;
  }
  if (q.type === 'number') {
    return (
      <NumberStepper
        value={value} onChange={setValue} label={q.title}
        min={q.numberMin} max={q.numberMax}
        unit={q.numberUnit} unitOne={q.numberUnitOne}
      />
    );
  }
  if (q.type === 'scale') {
    const ticks = [];
    for (let i = q.scaleMin; i <= q.scaleMax; i++) ticks.push(i);
    return (
      <div>
        <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11, color: 'var(--muted)', marginBottom: 10, fontFamily: 'var(--font-mono)', letterSpacing: '0.08em' }}>
          <span>{q.scaleLowLabel}</span>
          <span>{q.scaleHighLabel}</span>
        </div>
        <div style={{ display: 'flex', gap: 4 }}>
          {ticks.map(t => {
            const sel = value === t;
            return (
              <button key={t} onClick={() => setValue(t)} style={{
                flex: 1, height: 46, cursor: 'pointer',
                border: '1px solid ' + (sel ? 'var(--ink)' : 'var(--line)'),
                background: sel ? 'var(--ink)' : 'transparent',
                color: sel ? 'var(--paper)' : 'var(--ink)',
                fontFamily: 'var(--font-display)', fontSize: 16, borderRadius: 2,
                transition: 'all .12s',
              }}>{t}</button>
            );
          })}
        </div>
      </div>
    );
  }
  return null;
}

function PublicDropdownSearch({ q, value, setValue }) {
  const t = useT();
  const [open, setOpen] = React.useState(false);
  const [query, setQuery] = React.useState('');
  const [hi, setHi] = React.useState(0);
  const wrapRef = React.useRef(null);

  const isOther = typeof value === 'string' && value.startsWith('Other:');
  const otherText = isOther ? value.slice(6).trimStart() : '';

  // What text we show in the input.
  // When open, the user is typing a query; when closed and a value is selected, show the value.
  const inputValue = open ? query : (isOther ? '' : (value || ''));

  const filtered = React.useMemo(() => {
    const opts = q.options || [];
    if (!query.trim()) return opts;
    const Q = query.toLowerCase();
    return opts.filter(o => String(o).toLowerCase().includes(Q));
  }, [query, q.options]);

  // Close on outside click.
  React.useEffect(() => {
    if (!open) return;
    const onDown = (e) => {
      if (wrapRef.current && !wrapRef.current.contains(e.target)) {
        setOpen(false);
        setQuery('');
      }
    };
    window.addEventListener('mousedown', onDown);
    return () => window.removeEventListener('mousedown', onDown);
  }, [open]);

  const pick = (opt) => {
    setValue(opt);
    setOpen(false);
    setQuery('');
    setHi(0);
  };

  const commitOther = () => {
    setValue('Other: ' + query.trim());
    setOpen(false);
    setQuery('');
    setHi(0);
  };

  // Total selectable rows (filtered options + optional "Use Other" row when query is present)
  const otherRowShown = q.allowOther && query.trim().length > 0;
  const rowCount = filtered.length + (otherRowShown ? 1 : 0);

  const onKey = (e) => {
    if (e.key === 'ArrowDown') {
      e.preventDefault();
      if (!open) setOpen(true);
      setHi(h => Math.min(Math.max(0, rowCount - 1), h + 1));
    } else if (e.key === 'ArrowUp') {
      e.preventDefault();
      setHi(h => Math.max(0, h - 1));
    } else if (e.key === 'Enter') {
      e.preventDefault();
      if (hi < filtered.length) pick(filtered[hi]);
      else if (otherRowShown) commitOther();
    } else if (e.key === 'Escape') {
      setOpen(false);
      setQuery('');
    }
  };

  return (
    <div ref={wrapRef} style={{ position: 'relative' }}>
      <div style={{ position: 'relative' }}>
        <span style={{
          position: 'absolute', left: 12, top: '50%', transform: 'translateY(-50%)',
          color: 'var(--muted)', pointerEvents: 'none', display: 'flex',
        }}>{Icon.search(14)}</span>
        <input
          className="input"
          type="text"
          value={inputValue}
          placeholder={t('search')}
          onChange={e => { setQuery(e.target.value); setOpen(true); setHi(0); }}
          onFocus={() => { setOpen(true); setHi(0); }}
          onKeyDown={onKey}
          style={{ width: '100%', paddingLeft: 36, paddingRight: 36 }}
        />
        {value && !open && (
          <button
            type="button"
            onClick={() => setValue('')}
            aria-label={t('clear')}
            style={{
              position: 'absolute', right: 8, top: '50%', transform: 'translateY(-50%)',
              border: 'none', background: 'transparent', cursor: 'pointer',
              color: 'var(--muted)', padding: 4, display: 'flex',
            }}
          >{Icon.x(14)}</button>
        )}
      </div>

      {open && (
        <div style={{
          position: 'absolute', top: 'calc(100% + 4px)', left: 0, right: 0,
          maxHeight: 280, overflow: 'auto', background: 'var(--paper)',
          border: '1px solid var(--line)', borderRadius: 2,
          boxShadow: '0 12px 40px rgba(0,0,0,0.10)', zIndex: 30,
        }}>
          {filtered.length === 0 && !otherRowShown && (
            <div style={{ padding: '14px 16px', fontSize: 13, color: 'var(--muted)' }}>{t('noMatches')}</div>
          )}
          {filtered.map((o, i) => (
            <button
              key={o}
              type="button"
              onMouseDown={(e) => { e.preventDefault(); pick(o); }}
              onMouseEnter={() => setHi(i)}
              style={{
                display: 'block', width: '100%', textAlign: 'left',
                padding: '10px 14px', border: 'none',
                background: hi === i ? 'var(--paper-2)' : 'transparent',
                cursor: 'pointer', fontFamily: 'var(--font-ui)',
                fontSize: 14, color: 'var(--ink)',
              }}
            >{o}</button>
          ))}
          {otherRowShown && (
            <button
              type="button"
              onMouseDown={(e) => { e.preventDefault(); commitOther(); }}
              onMouseEnter={() => setHi(filtered.length)}
              style={{
                display: 'block', width: '100%', textAlign: 'left',
                padding: '10px 14px', border: 'none', borderTop: '1px solid var(--line)',
                background: hi === filtered.length ? 'var(--paper-2)' : 'transparent',
                cursor: 'pointer', fontFamily: 'var(--font-ui)',
                fontSize: 14, color: 'var(--ink)', fontStyle: 'italic',
              }}
            >{t('useOther', { q: query })}</button>
          )}
        </div>
      )}

      {isOther && !open && (
        <div style={{ marginTop: 8, fontSize: 12, color: 'var(--muted)' }}>
          {t('otherLabel')} <strong style={{ color: 'var(--ink)' }}>{otherText || t('empty')}</strong>
        </div>
      )}
    </div>
  );
}

function PublicVibe({ q, value, setValue }) {
  const t = useT();
  const labels = q.vibeLabels || VIBE_DEFAULTS;
  const groups = q.vibeGroups || [];
  const showHeaders = q.vibeGroupsEnabled !== false;
  const showCounter = q.vibeShowCounter !== false;
  const ans = (value && typeof value === 'object' && !Array.isArray(value)) ? value : {};

  const cycle = (itemId) => {
    const cur = ans[itemId];
    const next = { ...ans };
    if (cur === undefined) next[itemId] = 0;
    else if (cur === 0) next[itemId] = 1;
    else if (cur === 1) next[itemId] = 2;
    else delete next[itemId]; // 2 → back to untapped
    setValue(next);
  };

  // Counts across all items
  const counts = [0, 0, 0];
  let total = 0;
  for (const g of groups) {
    for (const it of (g.items || [])) {
      total++;
      const s = ans[it.id];
      if (s === 0 || s === 1 || s === 2) counts[s]++;
    }
  }
  const untapped = total - counts[0] - counts[1] - counts[2];

  return (
    <div>
      {showCounter && (
        <div style={{
          display: 'flex', gap: 12, justifyContent: 'space-between',
          marginBottom: 18, padding: '10px 0', borderBottom: '1px solid var(--line)',
          flexWrap: 'wrap',
        }}>
          {labels.map((lbl, i) => (
            <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13 }}>
              <span style={{
                width: 14, height: 14, borderRadius: '50%',
                background: VIBE_COLORS[i], border: '1.5px solid var(--ink)', flexShrink: 0,
              }}/>
              <span style={{ color: 'var(--muted)' }}>{lbl}</span>
              <span style={{ fontFamily: 'var(--font-mono)', fontSize: 13, color: 'var(--ink)' }}>{counts[i]}</span>
            </div>
          ))}
        </div>
      )}

      {groups.map(g => (
        <div key={g.id} style={{ marginBottom: 18 }}>
          {showHeaders && g.name && (
            <div style={{
              fontFamily: 'var(--font-display)', fontStyle: 'italic',
              fontSize: 16, color: 'var(--muted)',
              marginBottom: 12, paddingBottom: 4,
              borderBottom: '1px dotted var(--line-2)',
            }}>{g.name}</div>
          )}
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 12 }}>
            {(g.items || []).map(it => {
              const state = ans[it.id];
              const tapped = state === 0 || state === 1 || state === 2;
              const bg = tapped ? VIBE_COLORS[state] : 'var(--paper)';
              return (
                <button
                  key={it.id}
                  type="button"
                  onClick={() => cycle(it.id)}
                  aria-label={it.label + (tapped ? ' (' + labels[state] + ')' : ' (untapped)')}
                  style={{
                    display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6,
                    border: 'none', background: 'transparent', cursor: 'pointer', padding: 0,
                    userSelect: 'none',
                  }}
                >
                  <span style={{
                    width: 60, height: 60, borderRadius: '50%',
                    border: '1.5px solid var(--ink)', background: bg,
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                    fontFamily: 'var(--font-ui)', fontSize: 14, fontWeight: 600,
                    color: 'var(--ink)', transition: 'background .14s, transform .14s',
                  }}>{it.code}</span>
                  <span style={{
                    fontSize: 11, color: 'var(--muted)',
                    textAlign: 'center', lineHeight: 1.2,
                  }}>{it.label}</span>
                </button>
              );
            })}
          </div>
        </div>
      ))}

      {total > 0 && (
        <div style={{
          marginTop: 14, fontSize: 12, color: 'var(--muted)',
          fontFamily: 'var(--font-mono)', letterSpacing: '0.04em',
        }}>
          {untapped} {t('untapped')} = <em style={{ fontFamily: 'var(--font-display)', fontStyle: 'italic' }}>{t('dontKnow')}</em>
        </div>
      )}
    </div>
  );
}

window.PublicSurvey = PublicSurvey;
