// Admin terminál — VS-Code-like Claude Code UX.
// Layout: top toolbar (mode toggle, slash commands) + xterm.js (PTY output) + bottom composer
// (multiline textarea + file upload + send).
//
// Komunikácia:
//   - WebSocket /ws/admin-shell — interaktívny PTY (claude alebo bash)
//   - POST /api/admin-shell/upload — file → uloží do /tmp/.claude-uploads/<userId>/, vráti cestu;
//     klient potom vloží do PTY '@<path>' (claude CLI tak referuje súbory)

const TERM_THEME = {
  background: '#0e1a2b', foreground: '#e6eef7', cursor: '#f6bf47',
  selectionBackground: 'rgba(243,167,18,0.35)',
  black: '#0e1a2b',  red: '#ff6b6b',  green: '#7ed9a5', yellow: '#f6bf47',
  blue: '#6a91bb',   magenta: '#c084fc', cyan: '#7dd3fc', white: '#e6eef7',
  brightBlack: '#5b6b82', brightRed: '#ff8989', brightGreen: '#aef3c8',
  brightYellow: '#ffd878', brightBlue: '#8ab2dc', brightMagenta: '#d9b3ff',
  brightCyan: '#b3e5fc', brightWhite: '#ffffff',
};

// Slash-command shortcuts — zobrazené v toolbar. Pošle command + Enter cez PTY.
const SLASH_CMDS = [
  { label: '/clear',   hint: 'Vyčistiť konverzáciu' },
  { label: '/help',    hint: 'Zoznam príkazov' },
  { label: '/cost',    hint: 'Token usage v session' },
  { label: '/resume',  hint: 'Pokračovať v inej session' },
  { label: '/memory',  hint: 'Spravovať Claude pamäť' },
];

// Mode prepínač — claude CLI berie permission mode cez `--permission-mode <mode>` flag pri štarte.
// Zmena módu = reštart session (každý mode má vlastné nastavenia v config-e CLI).
//   default          — vždy sa pýta na potvrdenie (bezpečný default)
//   auto             — Claude rozhodne podľa heuristiky (vyžaduje --auto v CLI configu)
//   acceptEdits      — auto-akceptuje len Edit/Write tooly, ostatné sa pýta
//   bypassPermissions — YOLO mode: žiadne potvrdzovania, beží všetko
//   plan             — read-only plánovanie bez vykonávania zmien
const MODES = [
  { id: 'default',           label: 'Manuálne',     tooltip: 'Default — Claude sa pýta na každú akciu (najbezpečnejšie)' },
  { id: 'plan',              label: 'Plánovanie',   tooltip: 'Read-only — Claude pripraví plán bez zmien' },
  { id: 'acceptEdits',       label: 'Auto Edits',   tooltip: 'Auto-akceptuje edit/write tooly, ostatné (bash, web) sa pýta' },
  { id: 'bypassPermissions', label: 'YOLO',         tooltip: '⚠️ Bez potvrdzovania — všetko vrátane bash povelov beží automaticky' },
];

function AdminTerminal() {
  const containerRef = React.useRef(null);
  const termRef = React.useRef(null);
  const fitRef  = React.useRef(null);
  const wsRef   = React.useRef(null);
  const composerRef = React.useRef(null);

  const [status, setStatus] = React.useState('disconnected');
  const [cmd, setCmd] = React.useState('claude');           // claude | shell
  const [cwd, setCwd] = React.useState('/opt/ERP');
  const [reconnectKey, setReconnectKey] = React.useState(0);
  const [composer, setComposer] = React.useState('');
  const [uploads, setUploads] = React.useState([]);         // [{path, name, size}]
  const [uploading, setUploading] = React.useState(false);
  const [mode, setMode] = React.useState('default');
  const [dragOver, setDragOver] = React.useState(false);

  // Lock screen state — terminál vyžaduje 4-miestny PIN (oddelený od login hesla).
  const [lockState, setLockState] = React.useState({ loading: true, has_pin: false, has_passkeys: false, unlocked: false });

  const isSystem = (window.currentSession?.user?.role === 'system');

  // Pri mounte / po unlock-u skontrolujeme lock status.
  React.useEffect(() => {
    if (!isSystem) return;
    fetch('/api/admin-shell/status', { credentials: 'same-origin' })
      .then(r => r.json())
      .then(j => setLockState({
        loading: false,
        has_pin: !!(j.has_pin ?? j.has_password),
        has_passkeys: !!j.has_passkeys,
        unlocked: !!j.unlocked,
      }))
      .catch(() => setLockState({ loading: false, has_pin: false, has_passkeys: false, unlocked: false }));
  }, [isSystem, reconnectKey]);

  // ── WebSocket / xterm setup ──
  // Beží IBA keď je user sysadmin AND terminál je odomknutý (po zadaní hesla).
  React.useEffect(() => {
    if (!isSystem) return;
    if (!lockState.unlocked) return;
    if (!window.Terminal || !window.FitAddon) { setStatus('error'); return; }

    const term = new window.Terminal({
      cursorBlink: true,
      fontFamily: 'ui-monospace, Menlo, Monaco, "Cascadia Code", "JetBrains Mono", monospace',
      fontSize: 13, lineHeight: 1.25, theme: TERM_THEME,
      allowProposedApi: true, scrollback: 10000, convertEol: true,
    });
    const fit = new window.FitAddon.FitAddon();
    term.loadAddon(fit);
    term.open(containerRef.current);
    setTimeout(() => { try { fit.fit(); } catch {} }, 50);
    termRef.current = term;
    fitRef.current = fit;

    setStatus('connecting');
    const proto = location.protocol === 'https:' ? 'wss' : 'ws';
    const params = new URLSearchParams({ cmd, cwd });
    if (cmd === 'claude' && mode && mode !== 'default') params.set('mode', mode);
    const url = `${proto}://${location.host}/ws/admin-shell?${params.toString()}`;
    const ws = new WebSocket(url);
    wsRef.current = ws;

    ws.onopen = () => {
      setStatus('connected');
      ws.send('r' + JSON.stringify({ cols: term.cols, rows: term.rows }));
      term.focus();
    };
    ws.onmessage = (e) => {
      const msg = typeof e.data === 'string' ? e.data : '';
      if (!msg) return;
      const op = msg[0], body = msg.slice(1);
      if (op === 'o') term.write(body);
      else if (op === 'x') {
        term.writeln('\r\n\x1b[2m── proces ukončený (exit ' + body + ') ──\x1b[0m');
        setStatus('disconnected');
      } else if (op === 'e') {
        term.writeln('\r\n\x1b[31m✗ ' + body + '\x1b[0m');
        setStatus('error');
      }
    };
    ws.onclose = () => setStatus(s => s === 'error' ? s : 'disconnected');
    ws.onerror = () => {
      setStatus('error');
      term.writeln('\r\n\x1b[31m✗ WebSocket chyba\x1b[0m');
    };

    const sub = term.onData((data) => {
      if (ws.readyState === WebSocket.OPEN) ws.send('i' + data);
    });

    const onResize = () => {
      try {
        fit.fit();
        if (ws.readyState === WebSocket.OPEN) {
          ws.send('r' + JSON.stringify({ cols: term.cols, rows: term.rows }));
        }
      } catch {}
    };
    window.addEventListener('resize', onResize);
    const ro = new ResizeObserver(onResize);
    ro.observe(containerRef.current);

    return () => {
      window.removeEventListener('resize', onResize);
      ro.disconnect();
      sub.dispose();
      try { ws.close(); } catch {}
      try { term.dispose(); } catch {}
    };
    // eslint-disable-next-line
  }, [reconnectKey, cmd, cwd, mode, isSystem, lockState.unlocked]);

  // ── Helpers ──
  function ptySend(text) {
    const ws = wsRef.current;
    if (!ws || ws.readyState !== WebSocket.OPEN) return false;
    ws.send('i' + text);
    return true;
  }

  function sendComposer() {
    if (!composer.trim() && uploads.length === 0) return;
    // Composer text + file references (claude CLI: @/path/to/file)
    const fileRefs = uploads.map(u => `@${u.path}`).join(' ');
    const full = (composer.trim() + (fileRefs ? ' ' + fileRefs : '')).trim();
    if (!full) return;
    // Newline-stripped, ended with \r so claude prompt commits
    ptySend(full + '\r');
    setComposer('');
    setUploads([]);
    termRef.current?.focus();
  }

  function sendSlash(slash) {
    ptySend(slash + '\r');
    termRef.current?.focus();
  }

  function switchMode(modeId) {
    if (modeId === mode) return;
    if (modeId === 'bypassPermissions') {
      if (!confirm('⚠️ YOLO mode = Claude vykoná všetky tooly bez potvrdzovania (vrátane bash povelov, file edits, network calls).\n\nNaozaj zapnúť?')) return;
    }
    setMode(modeId);
    // Reconnect spustí claude s novým --permission-mode flagom (useEffect závisí na `mode`).
  }

  async function uploadFiles(fileList) {
    if (!fileList || fileList.length === 0) return;
    setUploading(true);
    const next = [...uploads];
    for (const f of Array.from(fileList)) {
      const fd = new FormData();
      fd.append('file', f);
      try {
        const r = await fetch('/api/admin-shell/upload', { method: 'POST', credentials: 'same-origin', body: fd });
        const j = await r.json();
        if (r.ok && j.ok) next.push({ path: j.path, name: f.name, size: f.size });
      } catch {}
    }
    setUploads(next);
    setUploading(false);
  }

  function reconnect() { setReconnectKey(k => k + 1); }

  if (!isSystem) {
    return (
      <AppShell active="terminal" crumbs={['Administrácia', 'Terminál']}>
        <div className="card" style={{padding:30, textAlign:'center', color:'var(--ink-500)'}}>
          🔒 Tento nástroj je dostupný iba pre rolu <b>Správca systému</b>.
        </div>
      </AppShell>
    );
  }

  // Lock screen — pred otvorením terminálu user musí zadať terminál heslo (alebo si ho nastaviť).
  if (lockState.loading) {
    return (
      <AppShell active="terminal" crumbs={['Administrácia', 'Terminál']}>
        <div className="card" style={{padding:30, textAlign:'center', color:'var(--ink-500)'}}>Načítavam…</div>
      </AppShell>
    );
  }
  if (!lockState.has_pin) {
    return (
      <AppShell active="terminal" crumbs={['Administrácia', 'Terminál']}>
        <TerminalSetupPin onSet={() => setReconnectKey(k => k + 1)}/>
      </AppShell>
    );
  }
  if (!lockState.unlocked) {
    return (
      <AppShell active="terminal" crumbs={['Administrácia', 'Terminál']}>
        <TerminalUnlock
          hasPasskeys={lockState.has_passkeys}
          onUnlocked={() => setLockState(s => ({ ...s, unlocked: true }))}
        />
      </AppShell>
    );
  }

  const statusChip = {
    disconnected: <span className="chip gray dot">Odpojené</span>,
    connecting:   <span className="chip amber dot">Pripájam…</span>,
    connected:    <span className="chip green dot">Pripojené</span>,
    error:        <span className="chip red dot">Chyba</span>,
  }[status];

  const currentMode = MODES.find(m => m.id === mode);

  return (
    <AppShell active="terminal" crumbs={['Administrácia', 'Terminál']}>
      <style>{`
        .term-wrap { display: flex; flex-direction: column; height: calc(100vh - 140px); min-height: 500px;
                     background: ${TERM_THEME.background}; border: 1px solid var(--border-strong);
                     border-radius: 10px; overflow: hidden; }
        .term-toolbar { display: flex; align-items: center; gap: 6px; padding: 8px 10px;
                        background: rgba(255,255,255,0.04); border-bottom: 1px solid rgba(255,255,255,0.08);
                        flex-wrap: wrap; }
        .term-tb-btn { background: rgba(255,255,255,0.06); color: #e6eef7; border: 1px solid rgba(255,255,255,0.1);
                       padding: 4px 9px; border-radius: 5px; font-size: 11.5px; font-family: var(--font);
                       cursor: pointer; height: 26px; display: inline-flex; align-items: center; gap: 4px;
                       transition: background 0.15s; }
        .term-tb-btn:hover { background: rgba(255,255,255,0.12); }
        .term-tb-btn.is-active { background: var(--amber-500); color: #0e1a2b; border-color: var(--amber-500); font-weight: 600; }
        .term-tb-btn.danger { background: rgba(255,107,107,0.15); border-color: rgba(255,107,107,0.3); color: #ff8989; }
        .term-tb-input { background: rgba(255,255,255,0.06); color: #e6eef7; border: 1px solid rgba(255,255,255,0.1);
                          padding: 0 8px; border-radius: 5px; font-size: 11.5px; height: 26px; font-family: ui-monospace, monospace; }
        .term-tb-sep { width: 1px; height: 18px; background: rgba(255,255,255,0.15); margin: 0 4px; }
        .term-tb-group { display: flex; gap: 2px; padding: 1px; background: rgba(255,255,255,0.04); border-radius: 6px; }
        .term-tb-group .term-tb-btn { border: none; background: transparent; height: 22px; padding: 0 7px; font-size: 11px; border-radius: 4px; }
        .term-tb-group .term-tb-btn.is-active { background: var(--navy-700); color: #fff; }
        .term-tb-label { font-size: 11px; color: rgba(230,238,247,0.6); margin-right: 2px; text-transform: uppercase; letter-spacing: 0.06em; font-weight: 600; }

        .term-body { flex: 1; position: relative; padding: 6px; }
        .term-body .xterm { height: 100%; }
        .term-body.is-drag::after { content: '⬇ Pustite súbor(y) sem'; position: absolute; inset: 6px;
                                    background: rgba(243,167,18,0.18); border: 2px dashed var(--amber-500);
                                    border-radius: 8px; display: grid; place-items: center; color: #f6bf47;
                                    font-weight: 700; font-size: 16px; pointer-events: none; z-index: 10; }

        .term-composer { display: flex; flex-direction: column; gap: 6px; padding: 8px 10px;
                          background: rgba(255,255,255,0.03); border-top: 1px solid rgba(255,255,255,0.08); }
        .term-upload-row { display: flex; flex-wrap: wrap; gap: 4px; }
        .term-upload-chip { display: inline-flex; align-items: center; gap: 4px; background: rgba(106,145,187,0.2);
                            color: #b8c9e0; padding: 2px 6px; border-radius: 10px; font-size: 11px;
                            border: 1px solid rgba(106,145,187,0.3); }
        .term-upload-chip button { background: transparent; border: none; color: inherit; cursor: pointer; padding: 0 0 0 2px; font-size: 13px; line-height: 1; }
        .term-composer-row { display: flex; gap: 6px; align-items: stretch; }
        .term-composer-textarea { flex: 1; background: rgba(255,255,255,0.06); color: #e6eef7;
                                   border: 1px solid rgba(255,255,255,0.12); border-radius: 6px;
                                   padding: 8px 10px; font-size: 13px; font-family: var(--font);
                                   resize: none; min-height: 38px; max-height: 200px; outline: none; }
        .term-composer-textarea:focus { border-color: var(--amber-500); }
        .term-composer-side { display: flex; flex-direction: column; gap: 4px; justify-content: flex-end; }
        .term-send-btn { background: var(--amber-500); color: #0e1a2b; border: none; padding: 0 14px;
                          height: 38px; border-radius: 6px; font-weight: 700; cursor: pointer;
                          display: inline-flex; align-items: center; gap: 6px; font-family: var(--font); font-size: 13px; }
        .term-send-btn:disabled { opacity: 0.5; cursor: not-allowed; }
        .term-attach-btn { background: rgba(255,255,255,0.08); color: #e6eef7; border: 1px solid rgba(255,255,255,0.12);
                           padding: 0 10px; height: 38px; border-radius: 6px; cursor: pointer;
                           display: inline-flex; align-items: center; gap: 4px; font-family: var(--font); font-size: 12px; }
      `}</style>

      <div className="page-head">
        <div>
          <h1>Terminál</h1>
          <p>
            {cmd === 'claude'
              ? <>Claude Code CLI · používa tvoj <b>claude.ai</b> subscription · VS-Code-like UX</>
              : <>Bash shell · plný root prístup na VPS</>}
          </p>
        </div>
        <div className="page-head-actions">
          {statusChip}
          <div className="seg" style={{marginLeft:8}}>
            <button className={cmd === 'claude' ? 'active' : ''} onClick={() => setCmd('claude')}>claude</button>
            <button className={cmd === 'shell'  ? 'active' : ''} onClick={() => setCmd('shell')}>bash</button>
          </div>
          <button className="btn" onClick={reconnect} disabled={status === 'connecting'}>
            <Ico.refresh/>{status === 'connected' ? 'Reštart' : 'Pripojiť'}
          </button>
        </div>
      </div>

      <div className="term-wrap">
        {/* Top toolbar */}
        <div className="term-toolbar">
          {cmd === 'claude' && <>
            <span className="term-tb-label">Mode</span>
            <div className="term-tb-group">
              {MODES.map(m => (
                <button
                  key={m.id}
                  className={`term-tb-btn${mode === m.id ? ' is-active' : ''}`}
                  onClick={() => switchMode(m.id)}
                  title={m.tooltip}
                >{m.label}</button>
              ))}
            </div>
            <span className="term-tb-sep"/>
            <span className="term-tb-label">Príkazy</span>
            {SLASH_CMDS.map(s => (
              <button key={s.label} className="term-tb-btn" onClick={() => sendSlash(s.label)} title={s.hint}>
                {s.label}
              </button>
            ))}
            <span className="term-tb-sep"/>
          </>}
          <span className="term-tb-label">cwd</span>
          <input
            className="term-tb-input"
            style={{width:200}}
            value={cwd}
            onChange={e => setCwd(e.target.value)}
            onBlur={() => reconnect()}
            placeholder="/opt/ERP"
            title="Pracovný adresár (zmena reštartuje session)"
          />
          <button
            className="term-tb-btn danger"
            style={{marginLeft:'auto'}}
            onClick={() => ptySend('\x03')}   // Ctrl+C
            title="Pošli SIGINT (Ctrl+C)"
          >Ctrl+C</button>
          <button
            className="term-tb-btn"
            onClick={() => { termRef.current?.clear(); }}
            title="Vyčistiť výstup terminálu (lokálne, neovplyvní claude session)"
          >Clear</button>
          <button
            className="term-tb-btn danger"
            onClick={async () => {
              await fetch('/api/admin-shell/lock', { method: 'POST', credentials: 'same-origin' });
              setLockState(s => ({ ...s, unlocked: false }));
              try { wsRef.current?.close(); } catch {}
            }}
            title="Uzamknúť terminál — bude potrebné znova zadať PIN"
          >🔒 Lock</button>
        </div>

        {/* PTY output (xterm) + drag-drop overlay */}
        <div
          className={`term-body${dragOver ? ' is-drag' : ''}`}
          onDragOver={e => { e.preventDefault(); setDragOver(true); }}
          onDragLeave={e => { e.preventDefault(); setDragOver(false); }}
          onDrop={e => {
            e.preventDefault(); setDragOver(false);
            uploadFiles(e.dataTransfer.files);
          }}
        >
          <div ref={containerRef} style={{ width:'100%', height:'100%' }}/>
        </div>

        {/* Bottom composer */}
        <div className="term-composer">
          {uploads.length > 0 && (
            <div className="term-upload-row">
              {uploads.map((u, i) => (
                <span key={i} className="term-upload-chip" title={u.path}>
                  📎 {u.name} · {(u.size/1024).toFixed(0)} kB
                  <button onClick={() => setUploads(uploads.filter((_, j) => j !== i))}>×</button>
                </span>
              ))}
            </div>
          )}
          <div className="term-composer-row">
            <textarea
              ref={composerRef}
              className="term-composer-textarea"
              placeholder={cmd === 'claude'
                ? 'Napíš správu pre Claude... (Enter = odoslať, Shift+Enter = nový riadok, @path referencuje súbor)'
                : 'Napíš bash príkaz... (Enter = vykonať)'}
              value={composer}
              onChange={e => setComposer(e.target.value)}
              onKeyDown={e => {
                if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendComposer(); }
              }}
              onPaste={e => {
                const items = Array.from(e.clipboardData?.files || []);
                if (items.length > 0) { e.preventDefault(); uploadFiles(items); }
              }}
              rows={2}
              disabled={status !== 'connected'}
            />
            <div className="term-composer-side">
              <label className="term-attach-btn" title="Pripojiť súbor (alebo drag-drop do terminálu)">
                {uploading ? '⏳' : '📎'}
                <span style={{fontSize:11}}>Súbor</span>
                <input type="file" multiple hidden onChange={e => { uploadFiles(e.target.files); e.target.value = ''; }}/>
              </label>
              <button
                className="term-send-btn"
                onClick={sendComposer}
                disabled={status !== 'connected' || (!composer.trim() && uploads.length === 0)}
                title="Odoslať do PTY (Enter)"
              >↵ Odoslať</button>
            </div>
          </div>
        </div>
      </div>

      <div className="small muted" style={{marginTop:8}}>
        💡 <b>Prvé spustenie:</b> claude vypíše OAuth URL — klikni v termináli a prihlas sa svojím claude.ai účtom.
        Token sa uloží na VPS, ďalšie spustenia ho použijú automaticky.
        <br/>
        🔒 Bash režim = plný root shell na VPS · všetky spawne v <code>audit_log</code>.
        Uploady ostávajú v <code>/tmp/.claude-uploads/&lt;userId&gt;/</code> 24h, potom sa mažú.
      </div>

      <TerminalPasskeyPanel onChange={() => setReconnectKey(k => k + 1)}/>
    </AppShell>
  );
}

// ── Setup PIN (prvý vstup, ešte nemá nastavený terminál PIN) ──
function TerminalSetupPin({ onSet }) {
  const [loginPwd, setLoginPwd] = React.useState('');
  const [pin, setPin] = React.useState('');
  const [confirm, setConfirm] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  const onlyDigits = (v) => v.replace(/\D+/g, '').slice(0, 4);
  async function submit(e) {
    e.preventDefault();
    if (!/^\d{4}$/.test(pin)) return setErr('PIN musí mať presne 4 číslice.');
    if (pin !== confirm) return setErr('Potvrdenie sa nezhoduje.');
    setBusy(true); setErr('');
    const r = await fetch('/api/admin-shell/set-password', {
      method: 'POST', credentials: 'same-origin',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ current_login_password: loginPwd, new_terminal_pin: pin }),
    });
    const j = await r.json();
    setBusy(false);
    if (r.ok && j.ok) onSet();
    else setErr(j.error === 'wrong_login_password' ? 'Login heslo je nesprávne.' : (j.message || 'Chyba.'));
  }
  return (
    <div className="card" style={{maxWidth:500, margin:'40px auto', padding:24}}>
      <div style={{textAlign:'center', fontSize:36, marginBottom:8}}>🔐</div>
      <h2 style={{margin:0, textAlign:'center'}}>Nastavte PIN terminálu</h2>
      <p className="muted" style={{textAlign:'center', marginTop:8}}>
        Terminál vyžaduje 4-miestny PIN (oddelený od login hesla) ako extra ochranná vrstva pred zneužitím v prípade ukradnutej session. Po 5 neúspešných pokusoch sa PIN uzamkne na 15 minút.
      </p>
      <form className="vstack" style={{gap:10, marginTop:20}} onSubmit={submit}>
        {err && <div className="auth-alert" role="alert"><span>{err}</span></div>}
        <div className="field">
          <label>Vaše login heslo (overenie)</label>
          <input className="input" type="password" value={loginPwd} onChange={e => setLoginPwd(e.target.value)} autoComplete="current-password" required/>
        </div>
        <div className="field">
          <label>Nový PIN (4 číslice)</label>
          <input
            className="input"
            type="password"
            inputMode="numeric"
            autoComplete="one-time-code"
            pattern="\d{4}"
            maxLength={4}
            value={pin}
            onChange={e => setPin(onlyDigits(e.target.value))}
            required
          />
        </div>
        <div className="field">
          <label>Potvrdiť PIN</label>
          <input
            className="input"
            type="password"
            inputMode="numeric"
            autoComplete="one-time-code"
            pattern="\d{4}"
            maxLength={4}
            value={confirm}
            onChange={e => setConfirm(onlyDigits(e.target.value))}
            required
          />
        </div>
        <button className="btn btn-primary btn-lg btn-block" type="submit" disabled={busy || !loginPwd || pin.length !== 4}>
          {busy ? 'Ukladám…' : 'Nastaviť PIN terminálu'}
        </button>
      </form>
    </div>
  );
}

// ── Unlock (PIN už existuje) ──
function TerminalUnlock({ onUnlocked, hasPasskeys }) {
  const [pin, setPin] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [bioBusy, setBioBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  const [attemptsLeft, setAttemptsLeft] = React.useState(null);
  const [lockedUntil, setLockedUntil] = React.useState(0);
  const [now, setNow] = React.useState(Date.now());
  const onlyDigits = (v) => v.replace(/\D+/g, '').slice(0, 4);
  const webauthnLib = window.SimpleWebAuthnBrowser;

  // Pri mounte zisti, či je rate-limit lockout aktívny.
  React.useEffect(() => {
    fetch('/api/admin-shell/status', { credentials: 'same-origin' })
      .then(r => r.json())
      .then(j => { if (j.locked_until && j.locked_until > Date.now()) setLockedUntil(j.locked_until); })
      .catch(() => {});
  }, []);

  // Countdown tick počas lockoutu.
  React.useEffect(() => {
    if (!lockedUntil || lockedUntil <= Date.now()) return;
    const id = setInterval(() => {
      const t = Date.now();
      setNow(t);
      if (t >= lockedUntil) { setLockedUntil(0); setErr(''); }
    }, 1000);
    return () => clearInterval(id);
  }, [lockedUntil]);

  const locked = lockedUntil > now;
  const secondsLeft = locked ? Math.ceil((lockedUntil - now) / 1000) : 0;

  async function submit(e) {
    e.preventDefault();
    if (!/^\d{4}$/.test(pin) || locked) return;
    setBusy(true); setErr('');
    const r = await fetch('/api/admin-shell/unlock', {
      method: 'POST', credentials: 'same-origin',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ pin }),
    });
    const j = await r.json();
    setBusy(false);
    if (r.ok && j.ok) { onUnlocked(); return; }
    if (j.error === 'rate_limited') {
      setLockedUntil(j.locked_until || 0);
      setAttemptsLeft(null);
      setErr(j.message || 'Príliš veľa neúspešných pokusov.');
    } else if (j.error === 'wrong_pin') {
      setAttemptsLeft(typeof j.attempts_left === 'number' ? j.attempts_left : null);
      setErr(typeof j.attempts_left === 'number'
        ? `Nesprávny PIN. Zostáva pokusov: ${j.attempts_left}.`
        : 'Nesprávny PIN.');
      setPin('');
    } else {
      setErr(j.message || 'Chyba.');
    }
  }

  return (
    <div className="card" style={{maxWidth:420, margin:'40px auto', padding:24}}>
      <div style={{textAlign:'center', fontSize:36, marginBottom:8}}>🔒</div>
      <h2 style={{margin:0, textAlign:'center'}}>Terminál je uzamknutý</h2>
      <p className="muted" style={{textAlign:'center', marginTop:8}}>
        Zadajte 4-miestny PIN terminálu. Odomknutie platí 1 hodinu, potom sa znova spýta.
      </p>
      <form className="vstack" style={{gap:10, marginTop:20}} onSubmit={submit}>
        {locked && (
          <div className="auth-alert" role="alert">
            <span>Uzamknuté po 5 nesprávnych pokusoch. Odblokovanie o <b>{secondsLeft}s</b>.</span>
          </div>
        )}
        {!locked && err && <div className="auth-alert" role="alert"><span>{err}</span></div>}
        <div className="field">
          <input
            className="input"
            type="password"
            inputMode="numeric"
            autoComplete="one-time-code"
            pattern="\d{4}"
            maxLength={4}
            value={pin}
            onChange={e => setPin(onlyDigits(e.target.value))}
            autoFocus
            placeholder="••••"
            disabled={busy || locked}
            style={{textAlign:'center', letterSpacing:'0.5em', fontSize:24}}
          />
        </div>
        <button className="btn btn-primary btn-lg btn-block" type="submit" disabled={busy || locked || pin.length !== 4}>
          {busy ? 'Overujem…' : 'Odomknúť PIN-om'}
        </button>
        {hasPasskeys && webauthnLib && (
          <>
            <div className="small muted" style={{textAlign:'center', margin:'4px 0'}}>— alebo —</div>
            <button
              type="button"
              className="btn btn-lg btn-block"
              disabled={bioBusy || locked}
              onClick={async () => {
                setBioBusy(true); setErr('');
                try {
                  const optR = await fetch('/api/admin-shell/passkey/unlock/options', { method:'POST', credentials:'same-origin' });
                  const optJ = await optR.json();
                  if (!optR.ok) throw new Error(optJ.message || optJ.error);
                  const assertion = await webauthnLib.startAuthentication({ optionsJSON: optJ.options });
                  const verR = await fetch('/api/admin-shell/passkey/unlock/verify', {
                    method:'POST', credentials:'same-origin',
                    headers: { 'Content-Type':'application/json' },
                    body: JSON.stringify({ response: assertion }),
                  });
                  const verJ = await verR.json();
                  if (verR.ok && verJ.ok) onUnlocked();
                  else throw new Error(verJ.message || verJ.error || 'verification_failed');
                } catch (e) {
                  setErr('Biometria zlyhala: ' + (e.message || e.name || 'unknown'));
                }
                setBioBusy(false);
              }}
              style={{background:'#fff', border:'1.5px solid var(--navy-700)', color:'var(--navy-700)', fontWeight:600}}
            >
              {bioBusy ? 'Čakám na biometriu…' : '👆 Odomknúť cez Touch ID / Face ID'}
            </button>
          </>
        )}
      </form>
    </div>
  );
}

// ── Passkey manažment — registrácia po unlock-u (PIN), zoznam, mazanie ──
function TerminalPasskeyPanel({ onChange }) {
  const [list, setList] = React.useState([]);
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  const [label, setLabel] = React.useState('');
  const webauthnLib = window.SimpleWebAuthnBrowser;

  async function load() {
    const r = await fetch('/api/admin-shell/passkey/list', { credentials:'same-origin' });
    const j = await r.json();
    if (r.ok && j.ok) setList(j.passkeys || []);
  }
  React.useEffect(() => { load(); }, []);

  async function register() {
    if (!webauthnLib) { setErr('Tento prehliadač nepodporuje WebAuthn.'); return; }
    setBusy(true); setErr('');
    try {
      const optR = await fetch('/api/admin-shell/passkey/register/options', { method:'POST', credentials:'same-origin' });
      const optJ = await optR.json();
      if (!optR.ok) throw new Error(optJ.message || optJ.error);
      const attestation = await webauthnLib.startRegistration({ optionsJSON: optJ.options });
      const verR = await fetch('/api/admin-shell/passkey/register/verify', {
        method:'POST', credentials:'same-origin',
        headers: { 'Content-Type':'application/json' },
        body: JSON.stringify({ response: attestation, label: label.trim() || (navigator.userAgent.match(/Mac|iPhone|iPad|Android|Windows/)?.[0] || 'Zariadenie') }),
      });
      const verJ = await verR.json();
      if (!verR.ok || !verJ.ok) throw new Error(verJ.message || verJ.error);
      setLabel('');
      await load();
      onChange && onChange();
    } catch (e) {
      setErr('Registrácia zlyhala: ' + (e.message || e.name || 'unknown'));
    }
    setBusy(false);
  }

  async function del(id) {
    if (!confirm('Zmazať tento passkey? Po vymazaní sa cez toto zariadenie nepripojíš.')) return;
    const r = await fetch('/api/admin-shell/passkey/' + id, { method:'DELETE', credentials:'same-origin' });
    if (r.ok) { await load(); onChange && onChange(); }
  }

  return (
    <div className="card" style={{padding:14, marginTop:14}}>
      <div className="card-head"><h3>🔐 Biometria — registrované zariadenia</h3></div>
      <div className="card-body vstack" style={{gap:10}}>
        {err && <div className="auth-alert" role="alert"><span>{err}</span></div>}
        {list.length === 0 && (
          <div className="small muted">
            Žiadne passkey ešte nie sú zaregistrované. Klikni „Zaregistrovať toto zariadenie" — Mac/iPhone si pýta Touch ID alebo Face ID.
          </div>
        )}
        {list.length > 0 && (
          <table className="tbl" style={{margin:0}}>
            <thead><tr><th>Zariadenie</th><th>Pridané</th><th>Naposledy</th><th></th></tr></thead>
            <tbody>
              {list.map(p => (
                <tr key={p.id}>
                  <td>👆 {p.label || '(bez popisu)'}</td>
                  <td className="small muted">{new Date(p.created_at * 1000).toLocaleDateString('sk-SK')}</td>
                  <td className="small muted">{p.last_used_at ? new Date(p.last_used_at * 1000).toLocaleString('sk-SK') : '—'}</td>
                  <td style={{textAlign:'right'}}><button className="btn btn-xs btn-danger" onClick={() => del(p.id)}><Ico.x/></button></td>
                </tr>
              ))}
            </tbody>
          </table>
        )}
        <div className="hstack" style={{gap:6}}>
          <input
            className="input"
            placeholder="Popis (napr. MacBook M2, iPhone 15 Pro)"
            value={label}
            onChange={e => setLabel(e.target.value)}
            maxLength={60}
            style={{flex:1}}
          />
          <button className="btn btn-primary" onClick={register} disabled={busy || !webauthnLib}>
            {busy ? 'Čakám…' : '+ Zaregistrovať toto zariadenie'}
          </button>
        </div>
        {!webauthnLib && (
          <div className="small muted">⚠️ Tento prehliadač nepodporuje WebAuthn — biometria nie je dostupná.</div>
        )}
      </div>
    </div>
  );
}

Object.assign(window, { AdminTerminal, TerminalPasskeyPanel });
