// Admin panel — Login + Shell + Overview + Files + Logs + Chat
const { useState: uS, useEffect: uE, useRef: uR, useCallback: uC } = React;
const { Button: BtnA, Ic: IcA } = window.UI;

/* ─── helpers ─── */
function fmtSize(b) {
  if (!b) return '0 B';
  const u = ['B','KB','MB','GB','TB'];
  let i = 0; let v = b;
  while (v >= 1024 && i < u.length - 1) { v /= 1024; i++; }
  return v.toFixed(i === 0 ? 0 : 1) + ' ' + u[i];
}

function fmtDate(ts) {
  if (!ts) return '—';
  return new Date(ts).toLocaleString('it-IT', { day:'2-digit', month:'2-digit', year:'numeric', hour:'2-digit', minute:'2-digit' });
}

function adminApi(path, opts = {}) {
  const token = localStorage.getItem('bt_admin_token');
  return fetch('/api' + path, {
    ...opts,
    headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json', ...(opts.headers || {}) },
    body: opts.body ? JSON.stringify(opts.body) : undefined,
  }).then(r => r.json());
}

/* ─── AdminLoginScreen ─── */
function AdminLoginScreen({ lang, onLogin }) {
  const t = window.I18N[lang].admin;
  const [email, setEmail] = uS('');
  const [pwd, setPwd]     = uS('');
  const [err, setErr]     = uS('');
  const [loading, setL]   = uS(false);

  const turnstileRef = uR(null);
  const turnstileWid = uR(null);
  const turnstileTokenRef = uR('');
  const turnstilePendingRef = uR(null);

  uE(() => {
    const siteKey = window.__BOLTON_TURNSTILE_SITE_KEY || '';
    if (!siteKey || siteKey === 'placeholder' || location.hostname === 'localhost' || location.hostname === '127.0.0.1') return;
    const renderWidget = () => {
      if (turnstileRef.current && window.turnstile && !turnstileWid.current) {
        turnstileWid.current = window.turnstile.render(turnstileRef.current, {
          sitekey: window.__BOLTON_TURNSTILE_SITE_KEY || '',
          size: 'invisible',
          callback: (token) => {
            turnstileTokenRef.current = token;
            if (turnstilePendingRef.current) {
              turnstilePendingRef.current(token);
              turnstilePendingRef.current = null;
            }
          },
          'expired-callback': () => { turnstileTokenRef.current = ''; },
        });
      }
    };
    if (window.turnstile) { renderWidget(); }
    else { document.addEventListener('turnstile:loaded', renderWidget, { once: true }); }
    return () => {
      if (turnstileWid.current && window.turnstile) {
        window.turnstile.remove(turnstileWid.current);
        turnstileWid.current = null;
      }
    };
  }, []);

  const waitForTurnstile = () => new Promise((resolve) => {
    turnstilePendingRef.current = resolve;
    window.turnstile.execute(turnstileWid.current);
    setTimeout(() => {
      if (turnstilePendingRef.current) {
        turnstilePendingRef.current(window.turnstile.getResponse(turnstileWid.current) || '');
        turnstilePendingRef.current = null;
      }
    }, 30000);
  });

  const submit = async (e) => {
    e.preventDefault();
    setErr('');

    if (!email || !email.includes('@') || !email.includes('.') || pwd.length < 8) {
      setErr(lang === 'it' ? 'Credenziali non valide' : 'Invalid credentials');
      return;
    }

    setL(true);
    try {
      const siteKey = window.__BOLTON_TURNSTILE_SITE_KEY || '';
      let token = '';
      if (siteKey && siteKey !== 'placeholder' && location.hostname !== 'localhost' && location.hostname !== '127.0.0.1' && window.turnstile) {
        token = window.turnstile.getResponse(turnstileWid.current);
        if (!token) {
          token = await waitForTurnstile();
          if (!token) {
            setErr(lang === 'it' ? 'Verifica di sicurezza fallita. Riprova.' : 'Security check failed. Please try again.');
            setL(false);
            return;
          }
        }
      }

      const res = await fetch('/api/admin/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email, password: pwd, turnstile: token }),
      });
      const data = await res.json();
      setL(false);
      if (!res.ok) { setErr(data.error || 'Errore'); return; }
      localStorage.setItem('bt_admin_token', data.token);
      onLogin();
    } catch {
      setL(false);
      setErr(lang === 'it' ? 'Errore di rete' : 'Network error');
    }
  };

  return (
    <div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'var(--canvas)' }}>
      <div className="card" style={{ width: 360, padding: '40px 36px' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 24 }}>
          <div className="brand-mark" style={{ background: 'var(--accent)', color: '#fff', borderRadius: 8, width: 36, height: 36, display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 700, fontSize: 16 }}>D</div>
          <div>
            <div style={{ fontWeight: 600, fontSize: 15, lineHeight: 1.2 }}>Dugongo Admin</div>
            <div style={{ fontSize: 12, color: 'var(--ink-soft)' }}>Bolton Transfer</div>
          </div>
        </div>
        <h2 style={{ margin: '0 0 6px', fontSize: 22, fontFamily: 'var(--font-serif)', fontWeight: 400 }}>{t.login_title}</h2>
        <p style={{ margin: '0 0 24px', fontSize: 13, color: 'var(--ink-soft)' }}>{t.login_sub}</p>
        {err && <div style={{ background: '#FEF2F2', border: '1px solid #FCA5A5', borderRadius: 8, padding: '10px 14px', marginBottom: 16, color: '#B91C1C', fontSize: 13 }}>{err}</div>}
        <form onSubmit={submit} style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
          <div>
            <label style={{ fontSize: 12.5, fontWeight: 500, display: 'block', marginBottom: 5 }}>{t.email}</label>
            <input className="input" placeholder={t.email_ph} value={email} onChange={e => setEmail(e.target.value)} type="email" required style={{ width: '100%' }} />
          </div>
          <div>
            <label style={{ fontSize: 12.5, fontWeight: 500, display: 'block', marginBottom: 5 }}>{t.pwd}</label>
            <input className="input" placeholder={t.pwd_ph} value={pwd} onChange={e => setPwd(e.target.value)} type="password" required autoComplete="current-password" style={{ width: '100%' }} />
          </div>
          <div ref={turnstileRef}></div>

          <button className="btn" data-variant="primary" data-block="true" disabled={loading} style={{ marginTop: 6 }}>
            {loading ? '…' : t.cta}
          </button>
        </form>
      </div>
    </div>
  );
}

/* ─── Stat card ─── */
function StatCard({ label, value, sub, icon }) {
  return (
    <div className="card" style={{ padding: '20px 24px' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
        <div>
          <div style={{ fontSize: 12.5, color: 'var(--ink-soft)', marginBottom: 6 }}>{label}</div>
          <div style={{ fontSize: 28, fontWeight: 600, lineHeight: 1.1 }}>{value}</div>
          {sub && <div style={{ fontSize: 12, color: 'var(--ink-soft)', marginTop: 4 }}>{sub}</div>}
        </div>
        <div style={{ color: 'var(--accent)', opacity: 0.7 }}>{icon}</div>
      </div>
    </div>
  );
}

/* ─── AdminOverview ─── */
function AdminOverview({ lang, onNav }) {
  const t = window.I18N[lang].admin;
  const [stats, setStats] = uS(null);

  uE(() => {
    adminApi('/admin/stats').then(setStats).catch(() => {});
  }, []);

  return (
    <div className="main">
      <div className="page-head">
        <div>
          <h1 className="page-title">{t.overview.title_a} <em>{t.overview.title_b}</em></h1>
          <p className="page-sub">{t.overview.sub}</p>
        </div>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: 16, marginBottom: 32 }}>
        <StatCard label={t.overview.files} value={stats ? stats.total_files : '…'} icon={<IcA.folder />} />
        <StatCard label={t.overview.size}  value={stats ? fmtSize(stats.total_size) : '…'} icon={<IcA.archive />} />
        <StatCard
          label={t.overview.last_upload}
          value={stats?.last_upload ? fmtDate(stats.last_upload) : t.overview.no_uploads}
          icon={<IcA.upload />}
        />
        <StatCard
          label={t.overview.unread}
          value={stats ? stats.unread_msgs : '…'}
          sub={stats?.unread_msgs > 0 ? <span style={{ color: '#B45309', cursor: 'pointer' }} onClick={() => onNav('chat')}>→ vai alla chat</span> : null}
          icon={<IcA.chat />}
        />
      </div>
    </div>
  );
}

/* ─── AdminFiles — Drive-style two-level nav ─── */
function AdminFiles({ lang }) {
  const t = window.I18N[lang].admin;
  const token = localStorage.getItem('bt_admin_token');

  const [path, setPath]         = uS([]);       // [{id, name}]
  const [folders, setFolders]   = uS([]);
  const [files, setFF]          = uS([]);
  const [loading, setLoading]   = uS(false);
  const [search, setSearch]     = uS('');
  const [preview, setPreview]   = uS(null);
  const [confirmDel, setCD]     = uS(null);
  const [zipping, setZipping]   = uS(false);

  const currentFolderId = path.length > 0 ? path[path.length - 1].id : null;

  const loadFolders = async (parentId) => {
    const qs = parentId ? '?parent_id=' + parentId : '?parent_id=';
    const data = await adminApi('/admin/folders' + qs);
    setFolders(data || []);
  };

  const loadFiles = async (folderId) => {
    const data = await adminApi('/admin/files?folder_id=' + folderId + '&limit=200');
    setFF(data.files || []);
  };

  uE(() => { loadFolders(null); }, []);

  const enterFolder = async (folder) => {
    setPath(prev => [...prev, { id: folder.id, name: folder.name }]);
    setSearch(''); setFolders([]); setFF([]); setLoading(true);
    await Promise.all([loadFolders(folder.id), loadFiles(folder.id)]);
    setLoading(false);
  };

  const goBack = async () => {
    const newPath = path.slice(0, -1);
    setPath(newPath);
    setSearch(''); setFolders([]); setFF([]); setLoading(true);
    const parentId = newPath.length > 0 ? newPath[newPath.length - 1].id : null;
    if (parentId) {
      await Promise.all([loadFolders(parentId), loadFiles(parentId)]);
    } else {
      await loadFolders(null);
      setFF([]);
    }
    setLoading(false);
  };

  const reloadAt = async (folderId) => {
    setFolders([]); setFF([]); setLoading(true);
    if (folderId) {
      await Promise.all([loadFolders(folderId), loadFiles(folderId)]);
    } else {
      await loadFolders(null);
      setFF([]);
    }
    setLoading(false);
  };

  const reloadCurrent = async () => {
    await reloadAt(currentFolderId);
  };

  const downloadZip = async (folderId) => {
    setZipping(true);
    try {
      const r = await fetch('/api/admin/zip-token', {
        method: 'POST',
        headers: { Authorization: 'Bearer ' + token, 'Content-Type': 'application/json' },
        body: JSON.stringify(folderId ? { folder_id: folderId } : {}),
      });
      if (!r.ok) throw new Error('Errore ZIP');
      const { token: dlToken, zipName } = await r.json();
      const a = document.createElement('a');
      a.href = '/api/admin/zip-download?token=' + dlToken;
      a.download = zipName;
      a.click();
    } catch (err) {
      alert(lang === 'it' ? 'Errore nella creazione dello ZIP.' : 'Error creating ZIP.');
    } finally {
      setZipping(false);
    }
  };

  const doDelete = async () => {
    const f = confirmDel; setCD(null);
    await adminApi('/admin/files/' + f.folder_id + '/' + f.id, { method: 'DELETE' });
    reloadCurrent();
  };

  const filteredFiles = files.filter(f =>
    !search || f.name.toLowerCase().includes(search.toLowerCase()) || (f.ext || '').toLowerCase().includes(search.toLowerCase())
  );

  const folderName = currentFolderId ? path[path.length - 1].name : '';

  /* ── Root view: folder grid ── */
  if (!currentFolderId) {
    return (
      <div className="main">
        <div className="page-head">
          <div>
            <h1 className="page-title">{t.files.title_a} <em>{t.files.title_b}</em></h1>
            <p className="page-sub">{t.files.sub}</p>
          </div>
          <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
            <BtnA icon={<IcA.download />} onClick={() => downloadZip(null)} disabled={zipping}>
              {zipping ? 'ZIP…' : 'Scarica tutto'}
            </BtnA>
          </div>
        </div>

        {loading && <div style={{ padding: 40, textAlign: 'center', color: 'var(--ink-soft)' }}>…</div>}
        {!loading && folders.length === 0 && <div style={{ padding: 40, textAlign: 'center', color: 'var(--ink-soft)' }}>{t.files.empty}</div>}

        <div className="grid-folders">
          {folders.map(fo => (
            <div className="folder-card" key={fo.id} onClick={() => enterFolder(fo)}>
              <div className="top">
                <div className="ic"><IcA.folder /></div>
              </div>
              <div>
                <div className="name">{fo.name}</div>
                <div className="meta" style={{ marginTop: 4 }}>
                  <span><b>{fo.files}</b> {lang === 'it' ? 'file' : 'files'}</span>
                  <span>·</span>
                  <span>{fmtSize(fo.size)}</span>
                  {fo.subfolders > 0 && <><span>·</span><span>{fo.subfolders} {lang === 'it' ? 'sottocartelle' : 'subfolders'}</span></>}
                </div>
              </div>
            </div>
          ))}
        </div>

        {confirmDel && <DeleteConfirm t={t} lang={lang} file={confirmDel} onCancel={() => setCD(null)} onConfirm={doDelete} />}
      </div>
    );
  }

  /* ── Inside folder view ── */
  return (
    <div className="main">
      {/* Breadcrumb */}
      <div className="crumb" style={{ marginBottom: 14 }}>
        <a onClick={goBack}>{t.files.title_a}</a>
        {path.map((p, i) => (
          <React.Fragment key={p.id}>
            <span className="sep">/</span>
            {i < path.length - 1
              ? <a onClick={async () => {
                  const newPath = path.slice(0, i + 1);
                  const fid = newPath[newPath.length - 1].id;
                  setPath(newPath);
                  await reloadAt(fid);
                }}>{p.name}</a>
              : <span className="cur">{p.name}</span>}
          </React.Fragment>
        ))}
      </div>

      <div className="page-head">
        <div>
          <h1 className="page-title" style={{ fontSize: 28 }}><IcA.folder style={{ verticalAlign: 'middle', marginRight: 8 }} />{folderName}</h1>
        </div>
        <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
          <input className="input" placeholder="Cerca file…" value={search}
            onChange={e => setSearch(e.target.value)} style={{ width: 180 }} />
          <BtnA icon={<IcA.download />} onClick={() => downloadZip(currentFolderId)} disabled={zipping || files.length === 0}>
            {zipping ? 'ZIP…' : 'Scarica tutto'}
          </BtnA>
        </div>
      </div>

      {loading && <div style={{ padding: 40, textAlign: 'center', color: 'var(--ink-soft)' }}>…</div>}

      {/* Subfolders */}
      {!loading && folders.length > 0 && (
        <>
          <div style={{ fontSize: 11.5, textTransform: 'uppercase', letterSpacing: '0.06em', color: 'var(--ink-soft)', marginBottom: 10 }}>
            {lang === 'it' ? 'Sottocartelle' : 'Subfolders'}
          </div>
          <div className="grid-folders" style={{ marginBottom: 24 }}>
            {folders.map(fo => (
              <div className="folder-card" key={fo.id} onClick={() => enterFolder(fo)} style={{ padding: 16, gap: 10 }}>
                <div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
                  <div className="ic" style={{ width: 36, height: 36, borderRadius: 8 }}><IcA.folder /></div>
                  <div style={{ minWidth: 0 }}>
                    <div className="name" style={{ fontSize: 14 }}>{fo.name}</div>
                    <div className="meta"><span><b>{fo.files}</b> file</span><span>·</span><span>{fmtSize(fo.size)}</span></div>
                  </div>
                </div>
              </div>
            ))}
          </div>
        </>
      )}

      {/* Files */}
      {!loading && (
        <>
          <div style={{ fontSize: 11.5, textTransform: 'uppercase', letterSpacing: '0.06em', color: 'var(--ink-soft)', marginBottom: 10 }}>
            File
          </div>
          {filteredFiles.length === 0 ? (
            <div style={{ padding: 40, textAlign: 'center', color: 'var(--ink-soft)' }}>{t.files.empty}</div>
          ) : (
            <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
              <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
                <thead>
                  <tr style={{ borderBottom: '1px solid var(--border)', background: 'var(--surface-soft)' }}>
                    {[t.files.col_name, t.files.col_ext, t.files.col_size, t.files.col_date, ''].map((h, i) => (
                      <th key={i} style={{ padding: '10px 16px', textAlign: 'left', fontWeight: 500, fontSize: 12, color: 'var(--ink-soft)' }}>{h}</th>
                    ))}
                  </tr>
                </thead>
                <tbody>
                  {filteredFiles.map(f => (
                    <tr key={f.id} style={{ borderBottom: '1px solid var(--border)' }}
                      onMouseEnter={e => e.currentTarget.style.background = 'var(--surface-soft)'}
                      onMouseLeave={e => e.currentTarget.style.background = ''}>
                      <td style={{ padding: '11px 16px' }}>
                        <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                          <span className="file-ic" style={{ background: window.UI.fileMeta(f.ext || '').c, fontSize: 9, minWidth: 28, height: 28, borderRadius: 6, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', fontWeight: 700 }}>
                            {window.UI.fileMeta(f.ext || '').l}
                          </span>
                          <div>
                            <div style={{ fontWeight: 500, maxWidth: 280, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }} title={f.name}>{f.name}</div>
                            {f.note && <div style={{ fontSize: 11.5, color: 'var(--ink-soft)', marginTop: 2, maxWidth: 280, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }} title={f.note}>{f.note}</div>}
                          </div>
                        </div>
                      </td>
                      <td style={{ padding: '11px 16px' }}><span className="badge">{f.ext}</span></td>
                      <td style={{ padding: '11px 16px', color: 'var(--ink-soft)', whiteSpace: 'nowrap' }}>{fmtSize(f.size)}</td>
                      <td style={{ padding: '11px 16px', color: 'var(--ink-soft)', fontSize: 12, whiteSpace: 'nowrap' }}>{fmtDate(f.created_at)}</td>
                      <td style={{ padding: '11px 16px' }}>
                        <div style={{ display: 'flex', gap: 6 }}>
                          {f.ext?.match(/^(jpg|jpeg|png|gif|webp|svg|mp4|webm|mov|m4v|pdf)$/) && (
                            <BtnA size="sm" variant="ghost" onClick={() => setPreview(f)}>{t.files.preview}</BtnA>
                          )}
                          <BtnA size="sm" variant="ghost" onClick={() => {
                            fetch('/api/folders/' + f.folder_id + '/files/' + f.id + '/download', { headers: { Authorization: 'Bearer ' + token } })
                              .then(r => r.blob()).then(blob => {
                                const url = URL.createObjectURL(blob);
                                const a = document.createElement('a'); a.href = url; a.download = f.name; a.click();
                                URL.revokeObjectURL(url);
                              });
                          }}>{t.files.download}</BtnA>
                          <BtnA size="sm" variant="ghost" onClick={() => setCD(f)} style={{ color: '#B91C1C' }}>{t.files.delete}</BtnA>
                        </div>
                      </td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          )}
        </>
      )}

      {/* Preview modal */}
      {preview && (
        <div className="modal-veil" onClick={() => setPreview(null)}>
          <div className="modal" style={{ maxWidth: 860, width: '90vw' }} onClick={e => e.stopPropagation()}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 14 }}>
              <span style={{ fontWeight: 500, fontSize: 14 }}>{preview.name}</span>
              <button className="iconbtn" onClick={() => setPreview(null)}><IcA.x /></button>
            </div>
            <PreviewEmbed file={preview} token={token} />
          </div>
        </div>
      )}

      {confirmDel && <DeleteConfirm t={t} lang={lang} file={confirmDel} onCancel={() => setCD(null)} onConfirm={doDelete} />}
    </div>
  );
}

function DeleteConfirm({ t, lang, file, onCancel, onConfirm }) {
  return (
    <div className="modal-veil" onClick={onCancel}>
      <div className="modal" style={{ maxWidth: 380 }} onClick={e => e.stopPropagation()}>
        <p style={{ margin: '0 0 20px', fontSize: 14 }}>{t.files.confirm_delete}</p>
        <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
          <BtnA variant="ghost" onClick={onCancel}>{window.I18N[lang].common.cancel}</BtnA>
          <BtnA variant="primary" onClick={onConfirm} style={{ background: '#DC2626', borderColor: '#DC2626' }}>{t.files.delete}</BtnA>
        </div>
      </div>
    </div>
  );
}

function PreviewEmbed({ file, token }) {
  const ext = file.ext?.toLowerCase();
  const [blobUrl, setBlobUrl] = uS(null);
  const [loading, setLoading] = uS(false);

  uE(() => {
    let revoked = false;
    setBlobUrl(null);
    setLoading(true);
    fetch(`/api/folders/${file.folder_id}/files/${file.id}/download`, {
      headers: { Authorization: 'Bearer ' + token },
    })
      .then(r => r.ok ? r.blob() : Promise.reject(r.status))
      .then(blob => { if (!revoked) setBlobUrl(URL.createObjectURL(blob)); })
      .catch(() => {})
      .finally(() => { if (!revoked) setLoading(false); });
    return () => {
      revoked = true;
      setBlobUrl(u => { if (u) URL.revokeObjectURL(u); return null; });
    };
  }, [file.id]);

  if (loading)  return <div style={{ padding: 40, textAlign: 'center', color: 'var(--ink-soft)' }}>…</div>;
  if (!blobUrl) return null;
  if (['mp4','webm','mov','m4v'].includes(ext))
    return <video src={blobUrl} controls style={{ width: '100%', maxHeight: '60vh', borderRadius: 6 }} />;
  if (['jpg','jpeg','png','gif','webp','svg'].includes(ext))
    return <img src={blobUrl} alt={file.name} style={{ maxWidth: '100%', maxHeight: '65vh', display: 'block', margin: 'auto', borderRadius: 6 }} />;
  if (ext === 'pdf')
    return <iframe src={blobUrl} style={{ width: '100%', height: '65vh', border: 'none', borderRadius: 6 }} title={file.name} />;
  return <div style={{ padding: 24, color: 'var(--ink-soft)', textAlign: 'center' }}>Anteprima non disponibile per .{ext}</div>;
}

/* ─── AdminLogs ─── */
function AdminLogs({ lang }) {
  const t = window.I18N[lang].admin;
  const [rows, setRows]       = uS([]);
  const [total, setTotal]     = uS(0);
  const [page, setPage]       = uS(1);
  const [filterAction, setFA] = uS('');
  const [filterFrom, setFF]   = uS('');
  const [filterTo, setFT]     = uS('');
  const [loading, setL]       = uS(false);
  const limit = 50;

  const load = uC(() => {
    setL(true);
    const q = new URLSearchParams({ page, limit });
    if (filterAction) q.set('action', filterAction);
    if (filterFrom)   q.set('from', filterFrom);
    if (filterTo)     q.set('to', filterTo);
    adminApi('/admin/logs?' + q).then(d => { setRows(d.rows || []); setTotal(d.total || 0); setL(false); }).catch(() => setL(false));
  }, [page, filterAction, filterFrom, filterTo]);

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

  const downloadCsv = () => {
    const q = new URLSearchParams({ format: 'csv' });
    if (filterAction) q.set('action', filterAction);
    if (filterFrom)   q.set('from', filterFrom);
    if (filterTo)     q.set('to', filterTo);
    const token = localStorage.getItem('bt_admin_token');
    fetch('/api/admin/logs?' + q, { headers: { Authorization: 'Bearer ' + token } })
      .then(r => r.blob()).then(blob => {
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a'); a.href = url; a.download = 'audit_log.csv'; a.click();
        URL.revokeObjectURL(url);
      });
  };

  const totalPages = Math.max(1, Math.ceil(total / limit));
  const ACTION_OPTS = ['login','login_fail','admin_login','admin_login_fail','upload','upload_rejected_magic','virus_quarantine','scan_error','download','preview','delete_file','admin_delete_file','folder_create','folder_update','folder_delete'];

  return (
    <div className="main">
      <div className="page-head">
        <div>
          <h1 className="page-title">{t.logs.title_a} <em>{t.logs.title_b}</em></h1>
          <p className="page-sub">{t.logs.sub}</p>
        </div>
        <BtnA size="sm" icon={<IcA.download />} onClick={downloadCsv}>{t.logs.export_csv}</BtnA>
      </div>

      {/* Filters */}
      <div style={{ display: 'flex', gap: 12, marginBottom: 18, flexWrap: 'wrap', alignItems: 'center' }}>
        <select className="input" style={{ minWidth: 200 }} value={filterAction} onChange={e => { setFA(e.target.value); setPage(1); }}>
          <option value="">{t.logs.all_actions}</option>
          {ACTION_OPTS.map(a => <option key={a} value={a}>{a}</option>)}
        </select>
        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          <label style={{ fontSize: 12.5, color: 'var(--ink-soft)', whiteSpace: 'nowrap' }}>{t.logs.filter_from}</label>
          <input type="date" className="input" value={filterFrom} onChange={e => { setFF(e.target.value); setPage(1); }} />
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          <label style={{ fontSize: 12.5, color: 'var(--ink-soft)', whiteSpace: 'nowrap' }}>{t.logs.filter_to}</label>
          <input type="date" className="input" value={filterTo} onChange={e => { setFT(e.target.value); setPage(1); }} />
        </div>
      </div>

      <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12.5 }}>
          <thead>
            <tr style={{ borderBottom: '1px solid var(--border)', background: 'var(--surface-soft)' }}>
              {[t.logs.col_ts, t.logs.col_action, t.logs.col_actor, t.logs.col_ip, t.logs.col_meta].map((h, i) => (
                <th key={i} style={{ padding: '10px 14px', textAlign: 'left', fontWeight: 500, fontSize: 12, color: 'var(--ink-soft)' }}>{h}</th>
              ))}
            </tr>
          </thead>
          <tbody>
            {loading && <tr><td colSpan={5} style={{ padding: 32, textAlign: 'center', color: 'var(--ink-soft)' }}>…</td></tr>}
            {!loading && rows.length === 0 && <tr><td colSpan={5} style={{ padding: 32, textAlign: 'center', color: 'var(--ink-soft)' }}>{t.logs.empty}</td></tr>}
            {rows.map(r => {
              let meta = '';
              try { const m = JSON.parse(r.meta); meta = Object.entries(m).map(([k,v]) => `${k}: ${v}`).join(' · '); } catch {}
              const isErr = r.action.includes('fail') || r.action.includes('reject') || r.action.includes('virus') || r.action.includes('error');
              return (
                <tr key={r.id} style={{ borderBottom: '1px solid var(--border)', background: isErr ? 'rgba(220,38,38,0.03)' : undefined }}>
                  <td style={{ padding: '9px 14px', whiteSpace: 'nowrap', color: 'var(--ink-soft)' }}>{fmtDate(r.ts)}</td>
                  <td style={{ padding: '9px 14px' }}>
                    <span className="badge" data-tone={isErr ? 'warn' : undefined}>{r.action}</span>
                  </td>
                  <td style={{ padding: '9px 14px', color: 'var(--ink-soft)' }}>{r.actor || '—'}</td>
                  <td style={{ padding: '9px 14px', color: 'var(--ink-soft)', fontFamily: 'var(--font-mono)', fontSize: 11 }}>{r.ip || '—'}</td>
                  <td style={{ padding: '9px 14px', color: 'var(--ink-soft)', maxWidth: 280, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }} title={meta}>{meta || '—'}</td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>

      {totalPages > 1 && (
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 16 }}>
          <span style={{ fontSize: 12, color: 'var(--ink-soft)' }}>{t.logs.page(page, totalPages)} · {total} eventi</span>
          <div style={{ display: 'flex', gap: 8 }}>
            <BtnA size="sm" variant="ghost" disabled={page <= 1} onClick={() => setPage(p => p - 1)}>{t.logs.prev}</BtnA>
            <BtnA size="sm" variant="ghost" disabled={page >= totalPages} onClick={() => setPage(p => p + 1)}>{t.logs.next}</BtnA>
          </div>
        </div>
      )}
    </div>
  );
}

/* ─── AdminChat ─── */
function AdminChat({ lang }) {
  const t = window.I18N[lang].admin;
  const [msgs, setMsgs]   = uS([]);
  const [draft, setDraft] = uS('');
  const [loading, setL]   = uS(false);
  const bottomRef = uR(null);
  const esRef     = uR(null);

  // Load history
  uE(() => {
    adminApi('/chat/messages').then(d => { if (Array.isArray(d)) setMsgs(d); }).catch(() => {});
    // Mark bolton messages as read
    adminApi('/chat/messages/read', { method: 'PATCH' }).catch(() => {});
  }, []);

  // SSE stream — uses a short-lived one-time ticket to avoid logging the JWT in the URL
  uE(() => {
    const token = localStorage.getItem('bt_admin_token');
    if (!token) return;
    let cancelled = false;
    fetch('/api/chat/sse-ticket', {
      method: 'POST',
      headers: { Authorization: 'Bearer ' + token },
    })
      .then(r => { if (!r.ok) throw new Error(r.status); return r.json(); })
      .then(({ ticket }) => {
        if (cancelled || !ticket) return;
        const es = new EventSource('/api/chat/stream?ticket=' + encodeURIComponent(ticket));
        esRef.current = es;
        es.onmessage = (e) => {
          try {
            const msg = JSON.parse(e.data);
            setMsgs(prev => {
              if (prev.some(m => m.id === msg.id)) return prev;
              // Mark as read immediately (admin is watching)
              if (msg.sender === 'bolton') {
                adminApi('/chat/messages/read', { method: 'PATCH' }).catch(() => {});
              }
              return [...prev, msg];
            });
          } catch {}
        };
      })
      .catch(() => {});
    return () => {
      cancelled = true;
      if (esRef.current) { esRef.current.close(); esRef.current = null; }
    };
  }, []);

  // Scroll to bottom on new messages
  uE(() => {
    bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [msgs]);

  const send = async () => {
    const text = draft.trim();
    if (!text) return;
    setDraft(''); setL(true);
    await adminApi('/chat/messages', { method: 'POST', body: { text } }).catch(() => {});
    setL(false);
  };

  return (
    <div className="main" style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
      <div className="page-head">
        <div>
          <h1 className="page-title">{t.chat.title_a} <em>{t.chat.title_b}</em></h1>
          <p className="page-sub">{t.chat.sub}</p>
        </div>
      </div>

      <div className="card" style={{ flex: 1, display: 'flex', flexDirection: 'column', padding: 0, overflow: 'hidden', minHeight: 400 }}>
        <div style={{ flex: 1, overflowY: 'auto', padding: '20px 24px', display: 'flex', flexDirection: 'column', gap: 12 }}>
          {msgs.length === 0 && <div style={{ textAlign: 'center', color: 'var(--ink-soft)', paddingTop: 40 }}>{t.chat.empty}</div>}
          {msgs.map(m => (
            <div key={m.id} style={{ display: 'flex', flexDirection: 'column', alignItems: m.sender === 'admin' ? 'flex-end' : 'flex-start' }}>
              <div style={{ fontSize: 11, color: 'var(--ink-soft)', marginBottom: 3 }}>
                {m.sender === 'admin' ? t.chat.admin_label : t.chat.bolton_label} · {fmtDate(m.ts)}
              </div>
              <div style={{
                maxWidth: '70%', padding: '10px 14px', borderRadius: 12, fontSize: 13.5, lineHeight: 1.5,
                background: m.sender === 'admin' ? 'var(--accent)' : 'var(--surface-soft)',
                color: m.sender === 'admin' ? '#fff' : 'var(--ink)',
                borderBottomRightRadius: m.sender === 'admin' ? 4 : 12,
                borderBottomLeftRadius:  m.sender === 'admin' ? 12 : 4,
              }}>
                {m.text}
              </div>
            </div>
          ))}
          <div ref={bottomRef} />
        </div>

        <div style={{ borderTop: '1px solid var(--border)', padding: '14px 16px', display: 'flex', gap: 8 }}>
          <input className="input" style={{ flex: 1 }} placeholder={t.chat.reply_ph} value={draft}
            onChange={e => setDraft(e.target.value)}
            onKeyDown={e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } }} />
          <BtnA variant="primary" onClick={send} disabled={!draft.trim() || loading}>{t.chat.send}</BtnA>
        </div>
      </div>
    </div>
  );
}

/* ─── AdminScreen (shell) ─── */
function AdminScreen({ lang, onLogout }) {
  const t = window.I18N[lang].admin;
  const [tab, setTab] = uS('overview');

  const navItems = [
    { key: 'overview', label: t.nav.overview, icon: <IcA.sparkle /> },
    { key: 'files',    label: t.nav.files,    icon: <IcA.folder /> },
    { key: 'logs',     label: t.nav.logs,     icon: <IcA.archive /> },
    { key: 'chat',     label: t.nav.chat,     icon: <IcA.chat /> },
  ];

  return (
    <div className="app">
      <header className="topbar">
        <div className="brand">
          <div className="brand-mark" style={{ background: 'var(--accent)', color: '#fff', borderRadius: 8 }}>D</div>
          <div>
            <div style={{ lineHeight: 1.1 }}>Dugongo <span className="serif-it" style={{ color: 'var(--ink-soft)' }}>admin</span></div>
          </div>
          <div className="brand-sub">Bolton Transfer</div>
        </div>
        <div className="topbar-actions">
          <button className="btn" data-variant="ghost" data-size="sm" onClick={onLogout}
            style={{ fontSize: 12.5 }}>
            {t.logout}
          </button>
        </div>
      </header>

      <div className="layout">
        <aside className="sidebar">
          <div className="side-section">
            {navItems.map(n => (
              <button key={n.key} className="side-link" data-active={tab === n.key} onClick={() => setTab(n.key)}>
                {n.icon} {n.label}
              </button>
            ))}
          </div>
          <hr className="hr" />
          <div style={{ padding: '12px 16px', fontSize: 12, color: 'var(--ink-soft)' }}>
            Pannello riservato<br />Dugongo · Admin
          </div>
        </aside>

        {tab === 'overview' && <AdminOverview lang={lang} onNav={setTab} />}
        {tab === 'files'    && <AdminFiles    lang={lang} />}
        {tab === 'logs'     && <AdminLogs     lang={lang} />}
        {tab === 'chat'     && <AdminChat     lang={lang} />}
      </div>
    </div>
  );
}

window.AdminLoginScreen = AdminLoginScreen;
window.AdminScreen      = AdminScreen;
