/* global React, ReactDOM, useT, LangSwitcher, MODULE_STEPS, pickLang */
const { useState, useEffect, useRef } = React;

// URL aplikacji — używany na wydruku karty z kodem. Hardcoded, bo w trybie
// dev (localhost) inaczej drukowałby się 127.0.0.1.
const APP_URL = 'https://decathlonlodz-antresola.pages.dev';

// ── Helpers ─────────────────────────────────────────
// currentModule z API to number (1..6) lub null gdy wszystko ukończone.
// Mapujemy na klucz tłumaczenia tytułu modułu.
function moduleLabel(n, t) {
  if (n == null) return t('mod.completed');
  return t(`mod.${n}.title`);
}

// Emoji dostępne w edytorze ogłoszeń.
const ANNOUNCEMENT_EMOJIS = ['📢','📣','⚠️','✅','❗','❓','🔴','🟢','🟡','📅','🕐','📦','🚚','👍','👏','🎉','✨','💪','🙂','ℹ️','📌','🔔','⭐','➡️'];

// Czy HTML jest "wizualnie pusty" (same tagi, bez tekstu)?
function htmlIsEmpty(html) {
  if (!html) return true;
  return !html.replace(/<[^>]*>/g, '').replace(/&nbsp;/g, ' ').trim();
}

// Sanityzacja HTML przed renderem — whitelist tagów, usuwa atrybuty/skrypty.
// Ogłoszenia piszą tylko adminowie, ale defense-in-depth nie zaszkodzi.
function sanitizeHtml(html) {
  if (!html) return '';
  if (!/[<>]/.test(html)) return html.replace(/\n/g, '<br>'); // zwykły tekst
  const ALLOWED = new Set(['B', 'STRONG', 'I', 'EM', 'U', 'UL', 'OL', 'LI', 'BR', 'P', 'DIV', 'SPAN', 'A']);
  const doc = new DOMParser().parseFromString(html, 'text/html');
  const clean = (node) => {
    [...node.childNodes].forEach((child) => {
      if (child.nodeType === 1) {
        clean(child); // najpierw dzieci (post-order)
        if (!ALLOWED.has(child.tagName)) {
          child.replaceWith(...child.childNodes); // odwiń niedozwolony tag
          return;
        }
        [...child.attributes].forEach((attr) => {
          const keep = child.tagName === 'A' && attr.name === 'href';
          if (!keep) child.removeAttribute(attr.name);
        });
        if (child.tagName === 'A') {
          const href = child.getAttribute('href') || '';
          if (/^\s*javascript:/i.test(href)) child.removeAttribute('href');
          else { child.setAttribute('target', '_blank'); child.setAttribute('rel', 'noopener noreferrer'); }
        }
      } else if (child.nodeType === 8) {
        child.remove(); // komentarze
      }
    });
  };
  clean(doc.body);
  return doc.body.innerHTML;
}

// ── RichTextEditor ──────────────────────────────────
// Prosty WYSIWYG (jak na forach): bold/italic/underline/lista + emoji.
// Przyciski podświetlają się, gdy formatowanie jest aktywne w miejscu kursora.
function RichTextEditor({ initialHtml, onChange, placeholder }) {
  const ref = useRef(null);
  const [showEmoji, setShowEmoji] = useState(false);
  const [active, setActive] = useState({ bold: false, italic: false, underline: false, list: false });

  useEffect(() => {
    if (ref.current) ref.current.innerHTML = initialHtml || '';
  }, []);

  const refreshActive = () => {
    try {
      setActive({
        bold: document.queryCommandState('bold'),
        italic: document.queryCommandState('italic'),
        underline: document.queryCommandState('underline'),
        list: document.queryCommandState('insertUnorderedList'),
      });
    } catch { /* queryCommandState może rzucić, gdy brak focusa */ }
  };

  const emit = () => { if (ref.current) onChange(ref.current.innerHTML); };

  const exec = (cmd) => {
    if (!ref.current) return;
    ref.current.focus();
    document.execCommand(cmd, false, null);
    emit();
    refreshActive();
  };

  const insertEmoji = (e) => {
    if (!ref.current) return;
    ref.current.focus();
    document.execCommand('insertText', false, e);
    emit();
    setShowEmoji(false);
  };

  // onMouseDown preventDefault — żeby klik w toolbar nie gubił zaznaczenia w edytorze.
  const noBlur = (ev) => ev.preventDefault();
  const cls = (on) => 'rte-btn' + (on ? ' active' : '');

  return (
    <div className="rte">
      <div className="rte-toolbar">
        <button type="button" className={cls(active.bold)} title="Pogrubienie" onMouseDown={noBlur} onClick={() => exec('bold')}><b>B</b></button>
        <button type="button" className={cls(active.italic)} title="Kursywa" onMouseDown={noBlur} onClick={() => exec('italic')}><i>I</i></button>
        <button type="button" className={cls(active.underline)} title="Podkreślenie" onMouseDown={noBlur} onClick={() => exec('underline')}><u>U</u></button>
        <button type="button" className={cls(active.list)} title="Lista" onMouseDown={noBlur} onClick={() => exec('insertUnorderedList')}>☰</button>
        <span className="rte-sep" />
        <button type="button" className={'rte-btn' + (showEmoji ? ' active' : '')} title="Emoji" onMouseDown={noBlur} onClick={() => setShowEmoji((v) => !v)}>😊</button>
        {showEmoji && (
          <div className="rte-emoji" onMouseDown={noBlur}>
            {ANNOUNCEMENT_EMOJIS.map((e) => (
              <button type="button" key={e} className="rte-emoji-item" onClick={() => insertEmoji(e)}>{e}</button>
            ))}
          </div>
        )}
      </div>
      <div
        ref={ref}
        className="rte-editor"
        contentEditable
        data-placeholder={placeholder}
        onInput={() => { emit(); refreshActive(); }}
        onKeyUp={refreshActive}
        onMouseUp={refreshActive}
        onFocus={refreshActive}
        suppressContentEditableWarning
      />
    </div>
  );
}

/* ──────────────────────────  HEADER  ────────────────────────── */

function Header({ screen, go, user, onLogout }) {
  const { t } = useT();
  const isAdmin = user && user.role === 'admin';
  const workerItems = [
    { id: 'welcome',       label: t('nav.start') },
    { id: 'panel',         label: t('nav.panel') },
    { id: 'announcements', label: t('nav.announcements') },
    { id: 'module',        label: t('nav.module') },
    { id: 'chat',          label: t('nav.chat') },
  ];
  const adminItems = [
    { id: 'admin',     label: t('nav.workers') },
    { id: 'admin-ann', label: t('nav.announcements') },
    { id: 'panel',     label: t('nav.modules') },
    { id: 'chat',      label: t('nav.chat') },
  ];
  const items = isAdmin ? adminItems : workerItems;

  return (
    <header className="header">
      <div className="header-inner">
        <div className="brand" onClick={() => user && go(isAdmin ? 'admin' : 'welcome')} style={{ cursor: user ? 'pointer' : 'default' }}>
          <img className="brand-logo" src="/assets/decathlon-horizontal.png" alt="Decathlon"
            onError={(e) => { e.currentTarget.style.display = 'none'; const w = e.currentTarget.nextElementSibling; if (w) w.style.display = 'inline'; }} />
          <span className="brand-word" style={{ display: 'none' }}>DECATHLON</span>
          <div className="brand-divider"></div>
          <div className="brand-section">
            {t('brand.section')}{isAdmin && <><span className="dot"></span>{t('brand.admin')}</>}
          </div>
        </div>
        {user && (
          <nav className="nav">
            {items.map(it => {
              // Admin: zakładka „Moduły" (panel) podświetlona też przy oglądaniu modułu.
              const active = screen === it.id || (isAdmin && it.id === 'panel' && screen === 'module');
              return (
                <button
                  key={it.id}
                  className={active ? 'active' : ''}
                  onClick={() => go(it.id)}
                >{it.label}</button>
              );
            })}
          </nav>
        )}
        <div className={'header-right' + (user ? '' : ' guest')}>
          <LangSwitcher />
          {user && (
            <div className="user-chip">
              <div className="avatar">{user.initials}</div>
              <div className="name">{user.name}</div>
              <button
                onClick={onLogout}
                className="logout-btn"
                title={t('header.logout')}
              >{t('header.logout')}</button>
            </div>
          )}
        </div>
      </div>
    </header>
  );
}

/* ──────────────────────────  LOGIN  ────────────────────────── */

function LoginScreen({ onLogin, initialCode = '' }) {
  const { t } = useT();
  const [code, setCode] = useState(initialCode);
  const [pin, setPin] = useState('');
  const [needPin, setNeedPin] = useState(false);
  const [err, setErr] = useState('');
  const [submitting, setSubmitting] = useState(false);

  const submit = async (e) => {
    e.preventDefault();
    const c = code.trim().toUpperCase();
    if (!c) { setErr(t('login.err_empty')); return; }
    if (needPin && !/^\d{6}$/.test(pin.trim())) { setErr(t('login.err_pin')); return; }

    setSubmitting(true);
    setErr('');
    try {
      const body = needPin ? { code: c, pin: pin.trim() } : { code: c };
      const res = await fetch('/api/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
      });
      if (res.status === 404) { setErr(t('login.err_unknown')); return; }
      if (res.status === 423) { setErr(t('login.err_locked')); return; }
      if (res.status === 401 && needPin) { setErr(t('login.err_pin_bad')); return; }
      if (!res.ok) { setErr('Błąd serwera. Spróbuj ponownie.'); return; }
      const user = await res.json();
      if (user.needPin) {
        // Admin — pokaż pole PIN i poczekaj na drugi submit.
        setNeedPin(true);
        return;
      }
      onLogin(needPin ? { ...user, pin: pin.trim() } : user);
    } catch {
      setErr('Brak połączenia z serwerem.');
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <div className="login-wrap fade-in">
      <div className="login-stack">
        <img className="login-logo" src="/assets/decathlon-symbol.png" alt="Decathlon"
          onError={(e) => { e.currentTarget.style.display = 'none'; }} />
      <div className="login-card">
        <span className="badge">{t('login.badge')}</span>
        <h1>{t('login.title')}</h1>
        <p>{t('login.subtitle')}</p>

        <form className="login-form" onSubmit={submit}>
          <label htmlFor="code">{t('login.label')}</label>
          <div className="input-wrap">
            <input
              id="code"
              type="text"
              placeholder={t('login.placeholder')}
              value={code}
              onChange={e => { setCode(e.target.value); setErr(''); }}
              className={err && !needPin ? 'error' : ''}
              autoFocus={!needPin}
              disabled={submitting || needPin}
            />
            {!needPin && (
              <button type="submit" className="btn btn-primary" disabled={submitting}>
                {submitting ? '…' : t('login.submit')}
              </button>
            )}
          </div>

          {needPin && (
            <div className="login-pin-row">
              <label htmlFor="pin">{t('login.pin_label')}</label>
              <div className="input-wrap">
                <input
                  id="pin"
                  type="password"
                  inputMode="numeric"
                  autoComplete="off"
                  placeholder="••••••"
                  value={pin}
                  maxLength={6}
                  onChange={e => { setPin(e.target.value.replace(/\D/g, '').slice(0, 6)); setErr(''); }}
                  className={err ? 'error' : ''}
                  autoFocus
                  disabled={submitting}
                />
                <button type="submit" className="btn btn-primary" disabled={submitting}>
                  {submitting ? '…' : t('login.submit')}
                </button>
              </div>
            </div>
          )}

          {err && <div className="err">{err}</div>}
        </form>

        <div className="login-help">
          <strong>{t('login.help_title')}</strong> {t('login.help_body')}<br/>
          <span style={{ color: 'var(--muted-2)' }}>
            {t('login.demo_prefix')} <code>LDZ-7K3M-9B4P</code> {t('login.demo_or')} <code>ADM-Q7K3-9B4P</code>.
          </span>
        </div>
      </div>
      </div>
    </div>
  );
}

/* ──────────────────────────  ADMIN  ────────────────────────── */

function AdminScreen({ user, setUser }) {
  const { t } = useT();
  const [query, setQuery] = useState('');
  const [copied, setCopied] = useState(null);
  const [workers, setWorkers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [showNewCode, setShowNewCode] = useState(false);
  const [showChangePin, setShowChangePin] = useState(false);
  const [detailWorker, setDetailWorker] = useState(null);
  const [printTarget, setPrintTarget] = useState(null);

  // Każde wywołanie admin-API wymaga code + pin (pin tylko w pamięci sesji).
  const auth = `code=${encodeURIComponent(user.code)}&pin=${encodeURIComponent(user.pin || '')}`;

  const loadWorkers = () => fetch(`/api/workers?${auth}`)
    .then((r) => (r.ok ? r.json() : []))
    .then((data) => setWorkers(data))
    .catch(() => setWorkers([]));

  // Druk karty z kodem — renderujemy PrintCard w portalu i wołamy window.print().
  const doPrint = (worker) => {
    setPrintTarget(worker);
    setTimeout(() => window.print(), 80);
  };

  useEffect(() => {
    let cancelled = false;
    fetch(`/api/workers?${auth}`)
      .then((r) => (r.ok ? r.json() : []))
      .then((data) => { if (!cancelled) setWorkers(data); })
      .catch(() => { if (!cancelled) setWorkers([]); })
      .finally(() => { if (!cancelled) setLoading(false); });
    return () => { cancelled = true; };
  }, [user.code]);

  const filtered = workers.filter(w =>
    !query ||
    w.name.toLowerCase().includes(query.toLowerCase()) ||
    w.code.toLowerCase().includes(query.toLowerCase())
  );

  const completed = workers.filter(w => w.done === w.total).length;
  const inProgress = workers.length - completed;

  const copy = (code) => {
    navigator.clipboard?.writeText(code);
    setCopied(code);
    setTimeout(() => setCopied(null), 1200);
  };

  return (
    <div className="container fade-in">
      <div className="admin-head">
        <div>
          <span className="eyebrow"><span className="dot"></span> {t('admin.eyebrow')}</span>
          <h2>{t('admin.h2')}</h2>
        </div>
        <div className="admin-head-actions">
          <button className="btn btn-ghost" onClick={() => setShowChangePin(true)}>{t('admin.change_pin')}</button>
          <button className="btn btn-secondary" onClick={() => setShowNewCode(true)}>{t('admin.new_code')}</button>
        </div>
      </div>

      <div className="admin-stats">
        <div className="admin-stat">
          <div className="lbl">{t('admin.stat_workers')}</div>
          <div className="val">{workers.length}</div>
        </div>
        <div className="admin-stat">
          <div className="lbl">{t('admin.stat_done')}</div>
          <div className="val" style={{ color: 'var(--green)' }}>{completed}</div>
        </div>
        <div className="admin-stat">
          <div className="lbl">{t('admin.stat_inprogress')}</div>
          <div className="val" style={{ color: 'var(--orange)' }}>{inProgress}</div>
        </div>
      </div>

      <div className="admin-table-wrap">
        <div className="admin-toolbar">
          <h3>{t('admin.workers_h3', { n: filtered.length })}</h3>
          <div className="search">
            <span style={{ fontSize: 13 }}>🔍</span>
            <input
              type="text"
              placeholder={t('admin.search_placeholder')}
              value={query}
              onChange={e => setQuery(e.target.value)}
            />
          </div>
        </div>
        <table className="admin-table">
          <thead>
            <tr>
              <th>{t('admin.th_worker')}</th>
              <th>{t('admin.th_progress')}</th>
              <th>{t('admin.th_step')}</th>
              <th>{t('admin.th_status')}</th>
              <th></th>
            </tr>
          </thead>
          <tbody>
            {filtered.map(w => {
              const pct = Math.round((w.done / w.total) * 100);
              const isDone = w.done === w.total;
              const barCls = isDone ? 'done' : '';
              return (
                <tr key={w.code}>
                  <td>
                    <div className="name-cell">
                      <div className="avx">{w.initials}</div>
                      <div>
                        <div style={{ fontWeight: 600 }}>{w.name}</div>
                        <div className="meta">{w.code}</div>
                      </div>
                    </div>
                  </td>
                  <td>
                    <div className="progress-cell">
                      <div className={'pbar ' + barCls}><i style={{ width: pct + '%' }}></i></div>
                      <span className="pnum">{w.done}/{w.total}</span>
                    </div>
                  </td>
                  <td style={{ color: 'var(--ink-2)' }}>{moduleLabel(w.currentModule, t)}</td>
                  <td>
                    {isDone
                      ? <span className="status-pill green">{t('admin.status_done')}</span>
                      : <span className="status-pill orange">{t('admin.status_progress')}</span>}
                  </td>
                  <td><button className="row-action" onClick={() => setDetailWorker(w)}>{t('admin.details')}</button></td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>

      <div className="admin-codes">
        <div className="admin-codes-list">
          <h3>{t('admin.codes_h3')}</h3>
          {workers.map(w => (
            <div className="code-row" key={w.code}>
              <div>
                <div className="c">{w.code}</div>
                <div className="for">{t('admin.codes_for', { name: w.name })}</div>
              </div>
              <div className="code-row-actions">
                <button
                  className={'copy-btn ' + (copied === w.code ? 'copied' : '')}
                  onClick={() => copy(w.code)}
                >{copied === w.code ? t('admin.copied') : t('admin.copy')}</button>
                <button className="copy-btn" onClick={() => doPrint(w)}>{t('admin.print')}</button>
              </div>
            </div>
          ))}
        </div>
        <div className="admin-codes-list">
          <h3>{t('admin.how_h3')}</h3>
          <div style={{ fontSize: 13, color: 'var(--muted)', lineHeight: 1.6 }}>
            <div style={{ marginBottom: 10 }}><strong style={{ color: 'var(--ink)' }}>1.</strong> {t('admin.how_1')}</div>
            <div style={{ marginBottom: 10 }}><strong style={{ color: 'var(--ink)' }}>2.</strong> {t('admin.how_2')}</div>
            <div><strong style={{ color: 'var(--ink)' }}>3.</strong> {t('admin.how_3')}</div>
          </div>
        </div>
      </div>

      {showNewCode && (
        <NewCodeModal
          adminCode={user.code}
          adminPin={user.pin}
          onClose={() => setShowNewCode(false)}
          onCreated={loadWorkers}
        />
      )}

      {showChangePin && (
        <ChangePinModal
          adminCode={user.code}
          onClose={() => setShowChangePin(false)}
          onChanged={(newPin) => { setUser((u) => ({ ...u, pin: newPin })); setShowChangePin(false); }}
        />
      )}

      {detailWorker && (
        <WorkerDetailModal
          worker={detailWorker}
          adminCode={user.code}
          adminPin={user.pin}
          onClose={() => setDetailWorker(null)}
          onPrint={doPrint}
          onDeleted={() => { setDetailWorker(null); loadWorkers(); }}
        />
      )}

      {printTarget && ReactDOM.createPortal(<PrintCard worker={printTarget} />, document.body)}
    </div>
  );
}

/* ──────────────────────────  WELCOME  ────────────────────────── */

function WelcomeScreen({ go, user, openModule }) {
  const { t } = useT();
  const remaining = user.total - user.done;
  return (
    <div className="container fade-in">
      <div className="welcome">
        <section className="welcome-head">
          <div>
            <span className="eyebrow">
              <span className="dot"></span> {t('welcome.eyebrow')}
            </span>
            <h1>{t('welcome.greet', { name: user.firstName })}</h1>
            <div className="ctas">
              <button className="btn btn-primary" onClick={() => go('panel')}>
                {t('welcome.cta_start')}
              </button>
              <button className="btn btn-secondary" onClick={() => go('chat')}>
                {t('welcome.cta_ask')}
              </button>
            </div>
          </div>
          <button className="next-step-card" onClick={() => openModule(user.currentModule || 1)}>
            <div className="ns-label">{t('welcome.next_label')}</div>
            <div className="ns-title">{moduleLabel(user.currentModule, t)}</div>
            <div className="ns-sub">{t('welcome.next_step', { n: user.currentStep })}</div>
          </button>
        </section>

        <div className="stat-row" style={{ gridTemplateColumns: 'repeat(2, 1fr)' }}>
          <div className="stat-card">
            <div className="stat-icon">✓</div>
            <div>
              <div className="stat-value">{user.done} / {user.total}</div>
              <div className="stat-label">{t('welcome.ctx_done_label')}</div>
            </div>
          </div>
          <div className="stat-card">
            <div className="stat-icon">📦</div>
            <div>
              <div className="stat-value">{remaining}</div>
              <div className="stat-label">{t('welcome.ctx_remaining_label')}</div>
            </div>
          </div>
        </div>

        <WelcomeAnnouncementsWidget go={go} code={user.code} />
      </div>
    </div>
  );
}

/* ──────────────────────────  PANEL  ────────────────────────── */

// Statyczne metadane modułów (title/desc z i18n + icon/accent/duration na stałe).
// Status (done/progress/todo) i progress% przychodzą z API w user.modules.
function getModules(t) {
  return [
    { id: 1, num: t('mod.1.num'), title: t('mod.1.title'), desc: t('mod.1.desc'),
      duration: '5:20',  icon: '👋', accent: '#3643BA', foot: t('mod.1.foot') },
    { id: 2, num: t('mod.2.num'), title: t('mod.2.title'), desc: t('mod.2.desc'),
      duration: '12:30', icon: '📱', accent: '#5B3FB7', foot: t('mod.2.foot') },
    { id: 3, num: t('mod.3.num'), title: t('mod.3.title'), desc: t('mod.3.desc'),
      duration: '7:10',  icon: '⚠', accent: '#D97706', foot: t('mod.3.foot') },
    { id: 4, num: t('mod.4.num'), title: t('mod.4.title'), desc: t('mod.4.desc'),
      duration: '6:45',  icon: '🗺', accent: '#1FAE6B', foot: t('mod.4.foot') },
    { id: 5, num: t('mod.5.num'), title: t('mod.5.title'), desc: t('mod.5.desc'),
      duration: '9:00',  icon: '📦', accent: '#0077B6', foot: t('mod.5.foot') },
    { id: 6, num: t('mod.6.num'), title: t('mod.6.title'), desc: t('mod.6.desc'),
      duration: '8:15',  icon: '🛟', accent: '#DC2626', foot: t('mod.6.foot') },
  ];
}

// Merguje statyczne metadane z postępem konkretnego użytkownika.
// Dokleja też `locked`: moduł 'todo' jest zablokowany, dopóki poprzedni nie
// jest 'done'. Moduły już rozpoczęte/ukończone zawsze otwieralne (do powtórki).
function mergeModules(metadata, userModules) {
  const byId = new Map((userModules || []).map((m) => [m.moduleId, m]));
  let allPrevDone = true; // czy WSZYSTKIE wcześniejsze moduły są ukończone
  return metadata.map((m) => {
    const um = byId.get(m.id);
    const status = um ? um.status : 'todo';
    const progress = um && um.status === 'progress'
      ? Math.round((um.currentStep / um.totalSteps) * 100)
      : undefined;
    const locked = status === 'todo' && !allPrevDone;
    if (status !== 'done') allPrevDone = false; // od pierwszego nieukończonego → reszta zablokowana
    return { ...m, status, progress, locked };
  });
}

function PanelScreen({ go, user, openModule }) {
  const { t } = useT();
  const modules = mergeModules(getModules(t), user.modules);
  const [filter, setFilter] = useState('all');
  const filtered = modules.filter(m => {
    if (filter === 'all') return true;
    if (filter === 'progress') return m.status === 'progress' || m.status === 'todo';
    if (filter === 'done') return m.status === 'done';
    return true;
  });

  const done = user.done;
  const total = user.total;
  const pct = Math.round((done / total) * 100);

  return (
    <div className="container fade-in">
      <div className="panel-head">
        <div>
          <span className="eyebrow"><span className="dot"></span> {t('panel.eyebrow')}</span>
          <h2>{t('panel.greet', { name: user.firstName })}</h2>
          <div className="sub">
            {t('panel.sub')}
            <strong>{t('panel.sub_done', { done })}{t('panel.sub_of', { total })}</strong>
            {t('panel.sub_next_prefix')}
            <strong>{moduleLabel(user.currentModule, t)}</strong>.
          </div>
        </div>
        <div className="progress-card">
          <ProgressRing pct={pct} />
          <div>
            <div className="ttl">{t('panel.ring_ttl')}</div>
            <div className="meta">{t('panel.ring_meta', { done, total })}</div>
            <div className="bar"><i style={{ width: pct + '%' }}></i></div>
          </div>
        </div>
      </div>

      <div className="section-title">
        <h3>{t('panel.section_h3')}</h3>
        <div className="filters">
          <button className={'chip ' + (filter==='all'?'active':'')} onClick={()=>setFilter('all')}>{t('panel.filter_all')}</button>
          <button className={'chip ' + (filter==='progress'?'active':'')} onClick={()=>setFilter('progress')}>{t('panel.filter_progress')}</button>
          <button className={'chip ' + (filter==='done'?'active':'')} onClick={()=>setFilter('done')}>{t('panel.filter_done')}</button>
        </div>
      </div>

      <div className="module-list">
        {filtered.map(m => (
          <ModuleRow key={m.id} m={m} onOpen={() => openModule(m.id)} t={t} />
        ))}
      </div>
    </div>
  );
}

function ProgressRing({ pct }) {
  const r = 18, c = 2 * Math.PI * r;
  const off = c * (1 - pct/100);
  return (
    <div className="ring">
      <svg width="44" height="44">
        <circle cx="22" cy="22" r={r} stroke="#EEF2F4" strokeWidth="4" fill="none" />
        <circle cx="22" cy="22" r={r} stroke="#3643BA" strokeWidth="4" fill="none"
          strokeDasharray={c} strokeDashoffset={off} strokeLinecap="round" />
      </svg>
      <div className="pct">{pct}%</div>
    </div>
  );
}

function ModuleRow({ m, onOpen, t }) {
  const locked = m.locked;
  return (
    <button
      className={'module-row' + (locked ? ' locked' : '')}
      onClick={locked ? undefined : onOpen}
      disabled={locked}
      style={{ '--accent': m.accent }}
    >
      <div className="accent"></div>
      <div className="icon">{locked ? '🔒' : m.icon}</div>
      <div>
        <div className="num">{m.num}</div>
        <div className="title">{m.title}</div>
      </div>
      <div className="desc">{m.desc}</div>
      <div className="stats">{m.foot}</div>
      <div>
        {locked                              && <span className="tag gray">{t('panel.tag_locked')}</span>}
        {!locked && m.status === 'done'      && <span className="tag green">{t('panel.tag_done')}</span>}
        {!locked && m.status === 'progress'  && <span className="tag orange">{t('panel.tag_progress', { pct: m.progress })}</span>}
        {!locked && m.status === 'todo'      && <span className="tag gray">{t('panel.tag_todo')}</span>}
      </div>
      <div className="duration">{m.duration}</div>
    </button>
  );
}

/* ──────────────────────────  MODULE VIEW (engine)  ────────────────────────── */

function getHotspots(t) {
  return [
    { n: 1, top: '60px', left: '8%',  title: t('hs.1.title'), desc: t('hs.1.desc') },
    { n: 2, top: '60px', left: '30%', title: t('hs.2.title'), desc: t('hs.2.desc') },
    { n: 3, top: '60px', left: '58%', title: t('hs.3.title'), desc: t('hs.3.desc') },
    { n: 4, top: '60px', left: '88%', title: t('hs.4.title'), desc: t('hs.4.desc') },
  ];
}

// Widget interaktywny: ekran Logpad z hotspotami.
function LogpadAnatomy() {
  const { t } = useT();
  const HOTSPOTS = getHotspots(t);
  const [activeHot, setActiveHot] = useState(null);
  return (
    <div className="logipad-wrap">
      <div className="logipad">
        <div className="screen-top">
          <span>{t('logipad.head_dev')}</span>
          <span className="battery">{t('logipad.battery')}</span>
        </div>
        <div className="lp-orow">
          <div className="lp-col lp-col-id">
            <div className="lp-zone">FIT4D1</div>
            <div className="lp-meta">20260510</div>
            <div className="lp-meta">n°390055</div>
          </div>
          <div className="lp-col lp-col-counts">
            <div className="lp-dead">15:00</div>
            <div className="lp-count-main">73 artykuły</div>
            <div className="lp-count-sec">40 adresy</div>
            <div className="lp-count-sec">47 kod EAN</div>
          </div>
          <div className="lp-col lp-col-codes">063(28), 064(7), 062(16), 006(6), 061(15), 068(1)</div>
          <div className="lp-col lp-col-cities">BIELA, LUBLI</div>
        </div>
        {HOTSPOTS.map(h => (
          <button key={h.n} className={'hotspot ' + (activeHot === h.n ? 'active' : '')}
            style={{ top: h.top, left: h.left }}
            onClick={() => setActiveHot(activeHot === h.n ? null : h.n)} aria-label={h.title}>{h.n}</button>
        ))}
      </div>
      <div className="legend">
        <div className="ttl">{t('module.legend_ttl')}</div>
        {HOTSPOTS.map(h => (
          <button key={h.n} className={'legend-item ' + (activeHot === h.n ? 'active' : (activeHot ? 'dim' : ''))}
            onClick={() => setActiveHot(activeHot === h.n ? null : h.n)}>
            <div className="pin">{h.n}</div>
            <div>
              <div className="ttl-row">{h.title}</div>
              <div className="desc">{h.desc}</div>
            </div>
          </button>
        ))}
      </div>
    </div>
  );
}

// Interaktywne zdjęcie z hotspotami (jak Logpad, ale na fotografii).
function ImageHotspots({ data, lang }) {
  const [active, setActive] = useState(null);
  const [ok, setOk] = useState(true);
  const points = data.points || [];
  if (!ok) return null; // brak pliku obrazka → ukryj cały blok (zamiast „zepsutego" obrazka)
  return (
    <div className={'imghot-wrap' + (data.wide ? ' wide' : '')}>
      <div className="imghot-stage">
        <img src={data.src} alt={pickLang(data.alt, lang)} loading="lazy" onError={() => setOk(false)} />
        {points.map((p) => (
          <button key={p.n}
            className={'hotspot ' + (active === p.n ? 'active' : '')}
            style={{ top: p.y + '%', left: p.x + '%' }}
            onClick={() => setActive(active === p.n ? null : p.n)}
            aria-label={pickLang(p.label, lang)}>{p.n}</button>
        ))}
      </div>
      <div className="legend">
        {points.map((p) => (
          <button key={p.n}
            className={'legend-item ' + (active === p.n ? 'active' : (active ? 'dim' : ''))}
            onClick={() => setActive(active === p.n ? null : p.n)}>
            <div className="pin">{p.n}</div>
            <div>
              <div className="ttl-row">{pickLang(p.label, lang)}</div>
              {p.desc && <div className="desc">{pickLang(p.desc, lang)}</div>}
            </div>
          </button>
        ))}
      </div>
    </div>
  );
}

// Renderer kroku typu 'text' — bloki: p / list / callout / cards / img / iconlist / imghotspots.
function StepText({ step, lang }) {
  return (
    <div className="step-text">
      {(step.blocks || []).map((b, i) => {
        if (b.p) return <p key={i} className="st-p">{pickLang(b.p, lang)}</p>;
        if (b.callout) return <div key={i} className="st-callout">{pickLang(b.callout, lang)}</div>;
        if (b.list) return (
          <ul key={i} className="st-list">
            {b.list.map((it, j) => <li key={j}>{pickLang(it, lang)}</li>)}
          </ul>
        );
        if (b.img) return (
          <figure key={i} className="st-figure">
            <img src={b.img.src} alt={pickLang(b.img.alt, lang)} loading="lazy"
              onError={(e) => { const f = e.currentTarget.closest('figure'); if (f) f.style.display = 'none'; }} />
            {b.img.caption && <figcaption>{pickLang(b.img.caption, lang)}</figcaption>}
          </figure>
        );
        if (b.iconlist) return (
          <ul key={i} className="st-iconlist">
            {b.iconlist.map((it, j) => (
              <li key={j}>
                <span className="ili-ic">{it.icon}</span>
                <span className="ili-tx">{pickLang(it.text, lang)}</span>
              </li>
            ))}
          </ul>
        );
        if (b.imghotspots) return <ImageHotspots key={i} data={b.imghotspots} lang={lang} />;
        if (b.cards) return (
          <div key={i} className="st-cards">
            {b.cards.map((c, j) => (
              <div key={j} className="st-card">
                <div className="st-card-label">{pickLang(c.label, lang)}</div>
                <div className="st-card-value">
                  {c.href
                    ? <a href={c.href} target="_blank" rel="noopener noreferrer">{c.value}</a>
                    : c.value}
                </div>
              </div>
            ))}
          </div>
        );
        if (b.sectors) return (
          <div key={i} className="st-sectors">
            {b.sectors.groups.map((g, gi) => (
              <div key={gi} className="st-sector" style={{ '--sector-accent': g.accent }}>
                <div className="st-sector-name">{g.name}</div>
                {g.items.map((it, ii) => (
                  <div key={ii} className="st-sector-row">
                    <span className="st-sector-code">{it.code}</span>
                    <span className="st-sector-label">{pickLang(it.label, lang)}</span>
                  </div>
                ))}
              </div>
            ))}
          </div>
        );
        if (b.norms) return (
          <div key={i} className="st-norms">
            {b.norms.groups.map((g, gi) => (
              <div key={gi} className="st-norm-group">
                <div className="st-norm-head"><span className="ico">{g.icon}</span>{pickLang(g.who, lang)}</div>
                {g.sections.map((sec, si) => (
                  <div key={si} className="st-norm-section">
                    <div className="st-norm-section-title">{pickLang(sec.title, lang)}</div>
                    {sec.rows.map((r, ri) => (
                      <div key={ri} className="st-norm-row">
                        <span className="nm-label">{pickLang(r.label, lang)}</span>
                        <span className="nm-value">{r.value}</span>
                      </div>
                    ))}
                  </div>
                ))}
              </div>
            ))}
          </div>
        );
        return null;
      })}
    </div>
  );
}

// Renderer kroku typu 'test'.
function StepTest({ step, lang, onPass }) {
  const { t } = useT();
  const [answers, setAnswers] = useState({});
  const [result, setResult] = useState(null); // { score, passed }
  const questions = step.questions || [];

  const check = () => {
    let correct = 0;
    questions.forEach((q, i) => { if (answers[i] === q.correct) correct++; });
    const score = Math.round((correct / questions.length) * 100);
    const passed = score >= (step.passScore || 80);
    setResult({ score, passed });
    if (passed) onPass();
  };

  const passScore = step.passScore || 80;
  const answeredCount = questions.filter((_, i) => answers[i] != null).length;
  const allAnswered = answeredCount === questions.length;

  return (
    <div className="step-test">
      {!result && (
        <div className="test-intro">
          <span className="ti-emoji">🎯</span>
          <div>
            <div className="ti-title">{t('test.intro_title')}</div>
            <div className="ti-sub">{t('test.intro_sub', { pass: passScore, n: questions.length })}</div>
          </div>
        </div>
      )}
      {questions.map((q, i) => (
        <div key={i} className="qst">
          <div className="qst-q">{i + 1}. {pickLang(q.q, lang)}</div>
          <div className="qst-opts">
            {q.options.map((opt, j) => {
              const picked = answers[i] === j;
              const showCorrect = result && j === q.correct;
              const showWrong = result && picked && j !== q.correct;
              return (
                <button key={j} type="button"
                  className={'qst-opt' + (picked ? ' picked' : '') + (showCorrect ? ' correct' : '') + (showWrong ? ' wrong' : '')}
                  onClick={() => !result && setAnswers(a => ({ ...a, [i]: j }))}
                  disabled={!!result}>
                  {pickLang(opt, lang)}
                </button>
              );
            })}
          </div>
        </div>
      ))}

      {!result && (
        <button className="btn btn-primary" disabled={!allAnswered} onClick={check}>
          {allAnswered ? t('test.check') : t('test.answered', { n: answeredCount, total: questions.length })}
        </button>
      )}
      {result && (
        <div className={'test-result ' + (result.passed ? 'pass' : 'fail')}>
          <div className="tr-emoji">{result.passed ? '🎉' : '💪'}</div>
          <div className="tr-score">{t('test.score', { score: result.score })}</div>
          <div className="tr-msg">{result.passed ? t('test.passed') : t('test.failed', { pass: step.passScore || 80 })}</div>
          {!result.passed && (
            <button className="btn btn-secondary" onClick={() => { setResult(null); setAnswers({}); }}>
              {t('test.retry')}
            </button>
          )}
        </div>
      )}
    </div>
  );
}

function ModuleScreen({ go, user, moduleId, onProgress }) {
  const { t, lang } = useT();
  const mid = moduleId || user.currentModule || 1;
  const steps = (typeof MODULE_STEPS !== 'undefined' && MODULE_STEPS[mid]) || [];
  const total = steps.length;

  const um = (user.modules || []).find(m => m.moduleId === mid);
  const initActive = (!um || um.status === 'done')
    ? 0
    : ((um.currentStep || 1) >= total - 1
        ? Math.max(0, total - 1)            // dobił do końca → ląduj na ostatnim kroku (quiz)
        : Math.max(0, Math.min((um.currentStep || 1) - 1, total - 1)));
  const [active, setActive] = useState(initActive);
  // Najdalej odblokowany krok. Naprzód tylko przez „Następny krok"; wstecz
  // (do już przerobionych) — dowolnie. Ukończony moduł → wszystko odblokowane.
  const [maxStep, setMaxStep] = useState(() => {
    if (um && um.status === 'done') return Math.max(0, total - 1);
    // Zapis postępu jest „przycięty" do total-1 (żeby nie oznaczyć modułu jako
    // 'done' przed zaliczeniem quizu). Dlatego gdy zapisany krok dobił do max,
    // odblokowujemy ostatni krok (quiz) — inaczej po odświeżeniu byłby zamknięty.
    if (um && (um.currentStep || 1) >= total - 1) return Math.max(0, total - 1);
    return initActive;
  });
  const [testPassed, setTestPassed] = useState(false);

  const step = steps[active];
  if (!step) {
    return (
      <div className="container fade-in">
        <button className="back-link" onClick={() => go('panel')}>{t('module.back')}</button>
        <p style={{ marginTop: 20, color: 'var(--muted)' }}>—</p>
      </div>
    );
  }

  const isLast = active === total - 1;
  const isTest = step.type === 'test';
  // Czy moduł jest ukończony (z API lub świeżo zaliczony test) — wtedy WSZYSTKIE
  // kroki są „zrobione", niezależnie od tego, który właśnie oglądamy.
  const moduleDone = (um && um.status === 'done') || testPassed;
  const progressPct = moduleDone ? 100 : Math.round((maxStep / total) * 100);

  // Podpis pod tytułem kroku w sidebarze: quiz → „Quiz", reszta → „Krok N".
  const stepSub = (s, i) => (s.type === 'test' ? t('module.step_quiz') : t('module.step_meta', { n: i + 1 }));

  // Zapis postępu w D1; clamp przy nawigacji żeby nie oznaczyć modułu 'done' przedwcześnie.
  const saveProgress = async (stepNum) => {
    if (user.role === 'admin') return; // admin tylko przegląda — nie zapisujemy postępu
    try {
      const res = await fetch('/api/progress', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ code: user.code, moduleId: mid, step: stepNum, totalSteps: total }),
      });
      if (res.ok && onProgress) onProgress(await res.json());
    } catch {}
  };

  const goPrev = () => setActive(a => Math.max(0, a - 1));
  const goNext = () => {
    const next = active + 1;
    setActive(next);
    setMaxStep(m => Math.max(m, next)); // odblokuj kolejny krok
    saveProgress(Math.min(next + 1, total - 1));
  };
  const finishModule = () => saveProgress(total); // status -> done

  return (
    <div className="container fade-in">
      <div className="module-view">
        <aside className="sidebar">
          <button className="back-link" onClick={() => go('panel')}>{t('module.back')}</button>
          <div className="eyebrow" style={{ marginBottom: 6 }}>{t('module.eyebrow_n', { n: String(mid).padStart(2, '0') })}</div>
          <h3>{t(`mod.${mid}.title`)}</h3>
          <div className="meta">{t(`mod.${mid}.desc`)}</div>
          <div className="bar"><i style={{ width: progressPct + '%' }}></i></div>
          <div className="pct">{t('module.step_meta', { n: active + 1 })} / {total}</div>

          <ul className="steps">
            {steps.map((s, i) => {
              const locked = i > maxStep;
              const completed = !locked && (i < maxStep || moduleDone); // przerobiony krok
              let cls = 'step';
              if (locked) cls += ' locked';
              else {
                if (completed) cls += ' done';
                if (i === active) cls += ' active';
              }
              return (
                <button key={i} className={cls}
                  onClick={locked ? undefined : () => setActive(i)}
                  disabled={locked}
                  title={locked ? t('module.step_locked_hint') : undefined}>
                  <div className="stepnum">{locked ? '🔒' : (completed ? '✓' : i + 1)}</div>
                  <div>
                    <div className="label">{pickLang(s.title, lang)}</div>
                    <div className="sub">{stepSub(s, i)}</div>
                  </div>
                </button>
              );
            })}
          </ul>
        </aside>

        <section className="lesson">
          <div className="breadcrumb">{t('module.step_meta', { n: active + 1 })}{isTest ? ' · ' + t('module.step_quiz') : ''}</div>
          <h2>{pickLang(step.title, lang)}</h2>

          {step.type === 'text' && <StepText step={step} lang={lang} />}
          {step.type === 'interactive' && step.widget === 'logpad' && <LogpadAnatomy />}
          {step.type === 'test' && <StepTest step={step} lang={lang} onPass={() => { setTestPassed(true); finishModule(); }} />}

          <div className="lesson-foot">
            <button className="btn btn-secondary" onClick={goPrev} disabled={active === 0}
              style={active === 0 ? { opacity: 0.5, cursor: 'not-allowed' } : {}}>{t('module.prev')}</button>
            <span className="step-meta">{t('module.step_meta', { n: active + 1 })} / {total}</span>
            {!isLast && <button className="btn btn-primary" onClick={goNext}>{t('module.next')}</button>}
            {isLast && !isTest && <button className="btn btn-primary" onClick={() => { finishModule(); go('panel'); }}>{t('module.finish')}</button>}
            {isLast && isTest && testPassed && <button className="btn btn-primary" onClick={() => go('panel')}>{t('module.finish')}</button>}
          </div>
        </section>
      </div>
    </div>
  );
}

/* ──────────────────────────  CHAT  ────────────────────────── */

function getSuggestions(t) {
  return [
    { id: 'zone_fit', text: t('chat.s_zone_fit') },
    { id: 'held',     text: t('chat.s_held') },
    { id: 'no_stock', text: t('chat.s_no_stock') },
  ];
}

function ChatScreen({ go, user }) {
  const { t, lang } = useT();
  const buildInitial = () => ([
    { role: 'bot', text: t('chat.greeting', { name: user.firstName }) },
  ]);

  const [messages, setMessages] = useState(buildInitial);
  const [input, setInput] = useState('');
  const [typing, setTyping] = useState(false);
  const scrollRef = useRef(null);

  // Po zmianie języka resetuje powitanie do wybranej wersji.
  useEffect(() => { setMessages(buildInitial()); /* eslint-disable-line */ }, [lang]);

  useEffect(() => {
    if (scrollRef.current) {
      scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
    }
  }, [messages, typing]);

  const suggestions = getSuggestions(t);

  // Prototyp: każde wejście (czip lub tekst) wywołuje tę samą "stock" odpowiedź.
  const sendStockReply = (userText) => {
    if (!userText || !userText.trim()) return;
    setMessages(m => [...m, { role: 'user', text: userText }]);
    setInput('');
    setTyping(true);
    setTimeout(() => {
      setMessages(m => [...m, { role: 'bot', text: t('chat.r_default') }]);
      setTyping(false);
    }, 800);
  };

  const askSuggestion = (s) => sendStockReply(s.text);
  const askText = (q) => sendStockReply(q);

  const userInitial = (user.initials || user.name || '?').toString()[0];

  return (
    <div className="container fade-in">
      <div className="chat-page solo">
        <section className="chat-shell">
          <div className="chat-head">
            <div className="who">
              <div className="av">AI</div>
              <div>
                <h3>{t('chat.head_h3')}</h3>
                <div className="sub">{t('chat.head_sub')}</div>
              </div>
            </div>
            <span className="online">{t('chat.online')}</span>
          </div>

          <div className="messages" ref={scrollRef}>
            {messages.map((m, i) => (
              <div key={i} className={'msg-row ' + m.role}>
                <div className="av">{m.role === 'bot' ? 'AI' : userInitial}</div>
                <div>
                  <div className="bubble">{m.text}</div>
                  {m.cite && (
                    <div style={{ marginTop: 6 }}>
                      <span className="cite"><span className="dotc"></span>{t('chat.cite_label')}: {m.cite}</span>
                    </div>
                  )}
                </div>
              </div>
            ))}
            {typing && (
              <div className="msg-row bot">
                <div className="av">AI</div>
                <div className="bubble">
                  <span className="typing"><i></i><i></i><i></i></span>
                </div>
              </div>
            )}
          </div>

          <div className="suggestions">
            {suggestions.map(s => (
              <button key={s.id} className="suggestion" onClick={() => askSuggestion(s)}>{s.text}</button>
            ))}
          </div>

          <form className="composer" onSubmit={e => { e.preventDefault(); askText(input); }}>
            <div className="field">
              <input
                type="text"
                placeholder={t('chat.placeholder')}
                value={input}
                onChange={e => setInput(e.target.value)}
              />
            </div>
            <button type="submit" className="send" disabled={!input.trim()} aria-label="Send">
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round">
                <path d="M22 2 L11 13"/>
                <path d="M22 2 L15 22 L11 13 L2 9 Z"/>
              </svg>
            </button>
          </form>
        </section>
      </div>
    </div>
  );
}

/* ──────────────────────────  ANNOUNCEMENTS  ────────────────────────── */

function formatAnnDate(unixSec, lang) {
  const locale = lang === 'uk' ? 'uk-UA' : 'pl-PL';
  const d = new Date(unixSec * 1000);
  const dateStr = d.toLocaleDateString(locale, { day: '2-digit', month: 'short', year: 'numeric' });
  const timeStr = d.toLocaleTimeString(locale, { hour: '2-digit', minute: '2-digit' });
  return `${dateStr} · ${timeStr}`;
}

function AnnouncementCard({ a }) {
  const { t, lang } = useT();
  const cls = 'ann-card ann-prio-' + a.priority;
  return (
    <article className={cls}>
      <header className="ann-card-head">
        <span className={'ann-badge ann-prio-' + a.priority}>{t('ann.priority_' + a.priority)}</span>
        <span className="ann-date">{formatAnnDate(a.created_at, lang)}</span>
      </header>
      <h4 className="ann-title">{a.title}</h4>
      <div className="ann-body" dangerouslySetInnerHTML={{ __html: sanitizeHtml(a.body) }} />
      <footer className="ann-foot">— {a.author_name}</footer>
    </article>
  );
}

// Widget na Welcome — pokazuje 2 najnowsze. Sam się ukrywa, jeśli pusto.
function WelcomeAnnouncementsWidget({ go, code }) {
  const { t } = useT();
  const [items, setItems] = useState([]);
  const [loaded, setLoaded] = useState(false);

  useEffect(() => {
    let cancelled = false;
    fetch(`/api/announcements?limit=2&code=${encodeURIComponent(code)}`)
      .then((r) => (r.ok ? r.json() : []))
      .then((data) => { if (!cancelled) setItems(data); })
      .catch(() => {})
      .finally(() => { if (!cancelled) setLoaded(true); });
    return () => { cancelled = true; };
  }, []);

  if (!loaded || items.length === 0) return null;

  return (
    <section className="ann-widget">
      <div className="ann-widget-head">
        <h3>{t('ann.welcome_h3')}</h3>
        <button className="link-btn" onClick={() => go('announcements')}>
          {t('ann.welcome_see_all')}
        </button>
      </div>
      <div className="ann-widget-list">
        {items.map((a) => <AnnouncementCard key={a.id} a={a} />)}
      </div>
    </section>
  );
}

// Pełna strona ogłoszeń (zakładka "Ogłoszenia" w nawigacji)
function AnnouncementsScreen({ user }) {
  const { t } = useT();
  const [items, setItems] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    let cancelled = false;
    fetch(`/api/announcements?code=${encodeURIComponent(user.code)}`)
      .then((r) => (r.ok ? r.json() : []))
      .then((data) => { if (!cancelled) setItems(data); })
      .catch(() => { if (!cancelled) setItems([]); })
      .finally(() => { if (!cancelled) setLoading(false); });
    return () => { cancelled = true; };
  }, []);

  return (
    <div className="container fade-in">
      <div className="ann-page">
        <h2>{t('ann.page_h2')}</h2>
        {loading && <p className="ann-empty">{t('ann.loading')}</p>}
        {!loading && items.length === 0 && <p className="ann-empty">{t('ann.empty')}</p>}
        <div className="ann-list">
          {items.map((a) => <AnnouncementCard key={a.id} a={a} />)}
        </div>
      </div>
    </div>
  );
}

// Sekcja w Admin: formularz + lista istniejących z usuwaniem.
function AdminAnnouncementsSection({ adminCode, adminPin }) {
  const { t } = useT();
  const [items, setItems] = useState([]);
  const [loading, setLoading] = useState(true);
  const [title, setTitle] = useState('');
  const [body, setBody] = useState('');
  const [priority, setPriority] = useState('normal');
  const [posting, setPosting] = useState(false);
  const [err, setErr] = useState('');
  const [editorKey, setEditorKey] = useState(0); // zmiana → remount edytora (czyszczenie)

  const bodyEmpty = htmlIsEmpty(body);

  const load = () => {
    setLoading(true);
    return fetch(`/api/announcements?code=${encodeURIComponent(adminCode)}`)
      .then((r) => (r.ok ? r.json() : []))
      .then((data) => setItems(data))
      .catch(() => setItems([]))
      .finally(() => setLoading(false));
  };

  useEffect(() => { load(); }, []);

  const submit = async (e) => {
    e.preventDefault();
    if (!title.trim() || bodyEmpty) return;
    setPosting(true);
    setErr('');
    try {
      const res = await fetch('/api/announcements', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ code: adminCode, pin: adminPin, title: title.trim(), body: body.trim(), priority }),
      });
      if (!res.ok) { setErr('Błąd publikacji'); return; }
      setTitle(''); setBody(''); setPriority('normal');
      setEditorKey((k) => k + 1); // wyczyść edytor
      await load();
    } catch {
      setErr('Brak połączenia');
    } finally {
      setPosting(false);
    }
  };

  const remove = async (id) => {
    if (!window.confirm(t('admin.ann_delete_confirm'))) return;
    try {
      const res = await fetch(`/api/announcements/${id}?code=${encodeURIComponent(adminCode)}&pin=${encodeURIComponent(adminPin || '')}`, {
        method: 'DELETE',
      });
      if (!res.ok) return;
      await load();
    } catch {}
  };

  return (
    <div className="admin-ann">
      <h3>{t('admin.ann_h3')}</h3>

      <form className="admin-ann-form" onSubmit={submit}>
        <h4>{t('admin.ann_new_title')}</h4>
        <input
          type="text"
          placeholder={t('admin.ann_field_title')}
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          maxLength={200}
          disabled={posting}
        />
        <RichTextEditor
          key={editorKey}
          initialHtml=""
          onChange={setBody}
          placeholder={t('admin.ann_field_body')}
        />
        <div className="admin-ann-form-foot">
          <select value={priority} onChange={(e) => setPriority(e.target.value)} disabled={posting}>
            <option value="normal">{t('ann.priority_normal')}</option>
            <option value="important">{t('ann.priority_important')}</option>
            <option value="event">{t('ann.priority_event')}</option>
          </select>
          <button
            type="submit"
            className="btn btn-primary"
            disabled={posting || !title.trim() || bodyEmpty}
          >
            {posting ? t('admin.ann_posting') : t('admin.ann_post')}
          </button>
        </div>
        {err && <div className="err" style={{ color: 'var(--red)', fontSize: 12 }}>{err}</div>}
      </form>

      <h4>{t('admin.ann_list')}</h4>
      {loading && <p className="ann-empty">{t('ann.loading')}</p>}
      {!loading && items.length === 0 && <p className="ann-empty">{t('ann.empty')}</p>}
      <div className="admin-ann-list">
        {items.map((a) => (
          <div key={a.id} className="admin-ann-item">
            <AnnouncementCard a={a} />
            <button className="btn-delete" onClick={() => remove(a.id)}>
              {t('admin.ann_delete')}
            </button>
          </div>
        ))}
      </div>
    </div>
  );
}

// Osobny ekran (zakładka "Ogłoszenia" w nawigacji admina)
function AdminAnnouncementsScreen({ user }) {
  return (
    <div className="container fade-in">
      <AdminAnnouncementsSection adminCode={user.code} adminPin={user.pin} />
    </div>
  );
}

/* ──────────────────────────  WORKER DETAIL MODAL  ────────────────────────── */

function WorkerDetailModal({ worker, adminCode, adminPin, onClose, onPrint, onDeleted }) {
  const { t } = useT();
  const modules = getModules(t);
  const byId = new Map((worker.modules || []).map((m) => [m.moduleId, m]));
  const [deleting, setDeleting] = useState(false);

  useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);

  const del = async () => {
    if (!window.confirm(t('admin.delete_confirm', { name: worker.name }))) return;
    setDeleting(true);
    try {
      const res = await fetch(
        `/api/workers/${encodeURIComponent(worker.code)}?adminCode=${encodeURIComponent(adminCode)}&pin=${encodeURIComponent(adminPin || '')}`,
        { method: 'DELETE' },
      );
      if (!res.ok) { setDeleting(false); return; }
      onDeleted();
    } catch {
      setDeleting(false);
    }
  };

  return ReactDOM.createPortal(
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal modal-wide" onClick={(e) => e.stopPropagation()}>
        <div className="wd-head">
          <div>
            <h3 className="modal-title" style={{ margin: 0 }}>{worker.name}</h3>
            <div className="wd-code">{worker.code}</div>
          </div>
          <button className="wd-close" onClick={onClose} aria-label="Close">✕</button>
        </div>

        <div className="wd-label">{t('admin.detail_progress')}</div>
        <ul className="wd-modules">
          {modules.map((m) => {
            const um = byId.get(m.id);
            const status = um ? um.status : 'todo';
            return (
              <li key={m.id} className="wd-module">
                <span className={'wd-dot wd-' + status}>
                  {status === 'done' ? '✓' : status === 'progress' ? '▶' : '·'}
                </span>
                <span className="wd-mod-title">{m.id}. {m.title}</span>
                <span className="wd-mod-status">
                  {status === 'done' && t('admin.status_done')}
                  {status === 'progress' && t('admin.detail_step', { n: um.currentStep, total: um.totalSteps })}
                  {status === 'todo' && t('admin.status_notstarted')}
                </span>
              </li>
            );
          })}
        </ul>

        <div className="modal-foot wd-foot">
          <button className="btn btn-secondary" onClick={() => onPrint(worker)}>
            🖨 {t('admin.detail_print')}
          </button>
          <button className="btn-delete" onClick={del} disabled={deleting}>
            {t('admin.detail_delete')}
          </button>
        </div>
      </div>
    </div>,
    document.body,
  );
}

/* ──────────────────────────  NEW CODE MODAL  ────────────────────────── */

function NewCodeModal({ adminCode, adminPin, onClose, onCreated }) {
  const { t } = useT();
  const [step, setStep] = useState('form'); // 'form' | 'generated'
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [creating, setCreating] = useState(false);
  const [created, setCreated] = useState(null);
  const [err, setErr] = useState('');

  // Esc zamyka modal
  useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);

  const submit = async (e) => {
    e.preventDefault();
    if (!firstName.trim() || !lastName.trim()) return;
    setCreating(true);
    setErr('');
    try {
      const res = await fetch('/api/workers/create', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          adminCode,
          pin: adminPin,
          firstName: firstName.trim(),
          lastName: lastName.trim(),
        }),
      });
      if (!res.ok) { setErr(t('modal.err_create')); return; }
      const data = await res.json();
      setCreated(data);
      setStep('generated');
      onCreated?.();
    } catch {
      setErr(t('modal.err_create'));
    } finally {
      setCreating(false);
    }
  };

  const print = () => window.print();

  // Renderujemy w portalu (do document.body), żeby uniknąć problemu z
  // transform-em na rodzicu (.container.fade-in tworzy containing block
  // dla position:fixed → modal lądowałby w środku kontenera, nie viewport).
  return ReactDOM.createPortal(
    <>
      <div className="modal-backdrop" onClick={onClose}>
        <div className="modal" onClick={(e) => e.stopPropagation()}>
          {step === 'form' && (
            <form onSubmit={submit}>
              <h3 className="modal-title">{t('modal.new_code_title')}</h3>

              <label className="modal-label">{t('modal.first_name')}</label>
              <input
                className="modal-input"
                type="text"
                value={firstName}
                onChange={(e) => setFirstName(e.target.value)}
                autoFocus
                maxLength={50}
                disabled={creating}
              />

              <label className="modal-label">{t('modal.last_name')}</label>
              <input
                className="modal-input"
                type="text"
                value={lastName}
                onChange={(e) => setLastName(e.target.value)}
                maxLength={50}
                disabled={creating}
              />

              {err && <div className="modal-err">{err}</div>}

              <div className="modal-foot">
                <button type="button" className="btn btn-secondary" onClick={onClose} disabled={creating}>
                  {t('modal.cancel')}
                </button>
                <button
                  type="submit"
                  className="btn btn-primary"
                  disabled={creating || !firstName.trim() || !lastName.trim()}
                >
                  {creating ? t('modal.generating') : t('modal.generate')}
                </button>
              </div>
            </form>
          )}

          {step === 'generated' && created && (
            <div className="modal-result">
              <div className="modal-ok-icon">✓</div>
              <h3 className="modal-title">{t('modal.code_generated')}</h3>
              <div className="result-name">{created.name}</div>
              <div className="result-code">{created.code}</div>

              <div className="modal-foot">
                <button className="btn btn-secondary" onClick={onClose}>
                  {t('modal.close')}
                </button>
                <button className="btn btn-primary" onClick={print}>
                  🖨 {t('modal.print')}
                </button>
              </div>
            </div>
          )}
        </div>
      </div>

      {/* Print-only widok A4. Na ekranie ukryty przez @media print w CSS. */}
      {step === 'generated' && created && <PrintCard worker={created} />}
    </>,
    document.body,
  );
}

function ChangePinModal({ adminCode, onClose, onChanged }) {
  const { t } = useT();
  const [currentPin, setCurrentPin] = useState('');
  const [newPin, setNewPin] = useState('');
  const [confirmPin, setConfirmPin] = useState('');
  const [saving, setSaving] = useState(false);
  const [err, setErr] = useState('');

  useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);

  const onlyDigits = (v) => v.replace(/\D/g, '').slice(0, 6);

  const submit = async (e) => {
    e.preventDefault();
    if (!/^\d{6}$/.test(currentPin)) { setErr(t('pin.err_current')); return; }
    if (!/^\d{6}$/.test(newPin)) { setErr(t('pin.err_invalid')); return; }
    if (newPin !== confirmPin) { setErr(t('pin.err_mismatch')); return; }
    setSaving(true);
    setErr('');
    try {
      const res = await fetch('/api/admin/pin', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ code: adminCode, pin: currentPin, newPin }),
      });
      if (res.status === 423) { setErr(t('login.err_locked')); return; }
      if (res.status === 401) { setErr(t('pin.err_current')); return; } // zły obecny PIN
      if (!res.ok) { setErr(t('pin.err_save')); return; }
      onChanged(newPin);
    } catch {
      setErr(t('pin.err_save'));
    } finally {
      setSaving(false);
    }
  };

  return ReactDOM.createPortal(
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        <form onSubmit={submit}>
          <h3 className="modal-title">{t('pin.title')}</h3>
          <p className="modal-sub">{t('pin.sub')}</p>

          <label className="modal-label">{t('pin.current')}</label>
          <input
            className="modal-input"
            type="password"
            inputMode="numeric"
            autoComplete="off"
            placeholder="••••••"
            value={currentPin}
            maxLength={6}
            onChange={(e) => { setCurrentPin(onlyDigits(e.target.value)); setErr(''); }}
            autoFocus
            disabled={saving}
          />

          <label className="modal-label">{t('pin.new')}</label>
          <input
            className="modal-input"
            type="password"
            inputMode="numeric"
            autoComplete="off"
            placeholder="••••••"
            value={newPin}
            maxLength={6}
            onChange={(e) => { setNewPin(onlyDigits(e.target.value)); setErr(''); }}
            disabled={saving}
          />

          <label className="modal-label">{t('pin.confirm')}</label>
          <input
            className="modal-input"
            type="password"
            inputMode="numeric"
            autoComplete="off"
            placeholder="••••••"
            value={confirmPin}
            maxLength={6}
            onChange={(e) => { setConfirmPin(onlyDigits(e.target.value)); setErr(''); }}
            disabled={saving}
          />

          {err && <div className="modal-err">{err}</div>}

          <div className="modal-foot">
            <button type="button" className="btn btn-secondary" onClick={onClose} disabled={saving}>
              {t('modal.cancel')}
            </button>
            <button type="submit" className="btn btn-primary" disabled={saving || !currentPin || !newPin || !confirmPin}>
              {saving ? t('pin.saving') : t('pin.save')}
            </button>
          </div>
        </form>
      </div>
    </div>,
    document.body,
  );
}

function PrintCard({ worker }) {
  const { t } = useT();
  // Etykiety zakładek bierzemy z nav.* — żeby zgadzało się 1:1 z tym, co
  // pracownik realnie zobaczy w nawigacji w wybranym języku.
  const tabs = [
    { label: t('nav.start'),         desc: t('print.tab_start_desc') },
    { label: t('nav.panel'),         desc: t('print.tab_panel_desc') },
    { label: t('nav.announcements'), desc: t('print.tab_ann_desc') },
    { label: t('nav.module'),        desc: t('print.tab_module_desc') },
    { label: t('nav.chat'),          desc: t('print.tab_assistant_desc') },
  ];
  return (
    <div className="print-area">
      <header className="pc-brand">
        <div className="pc-decathlon">DECATHLON</div>
        <div className="pc-divider"></div>
        <div className="pc-loc">{t('print.location')}</div>
      </header>

      <h1 className="pc-h1">{t('print.h1')}</h1>

      <section className="pc-block">
        <div className="pc-label">{t('print.name_label')}</div>
        <div className="pc-name">{worker.name}</div>
      </section>

      <section className="pc-block pc-code-block">
        <div className="pc-label">{t('print.code_label')}</div>
        <div className="pc-code">{worker.code}</div>
      </section>

      <section className="pc-block">
        <div className="pc-label">{t('print.url_label')}</div>
        <div className="pc-url">{APP_URL}</div>
      </section>

      <section className="pc-block pc-how">
        <div className="pc-label">{t('print.how_h')}</div>
        <p className="pc-intro">{t('print.how_intro')}</p>
        <ul className="pc-tabs">
          {tabs.map((tab) => (
            <li key={tab.label}>
              <strong>{tab.label}</strong> — {tab.desc}
            </li>
          ))}
        </ul>
      </section>

      <footer className="pc-footer">{t('print.footer')}</footer>
    </div>
  );
}

Object.assign(window, { Header, LoginScreen, WelcomeScreen, PanelScreen, ModuleScreen, ChatScreen, AdminScreen, AnnouncementsScreen, AdminAnnouncementsScreen });
