// ─────────────────────────────────────────────────────────────
// screens-integrity.jsx — assessment integrity + offline survival
// IntegrityBanner (visibility-loss tracking, paste blocker, fullscreen prompt),
// ServerTimeReconciler (banner + drift fix), IDB autosave hook for attempts,
// AssessmentRecoverySheet (resume from local saves)
// ─────────────────────────────────────────────────────────────
const { useState: useStateI, useEffect: useEffectI, useRef: useRefI, useMemo: useMemoI, useCallback: useCallbackI } = React;

// ─────────────────────────────────────────────────────────────
// useIntegrity — wires up:
//   • visibilitychange tracking (3 strikes warning, 5 strikes auto-submit)
//   • paste blocker + reason logging
//   • copy/cut blocker
//   • right-click block
//   • selection block while attempting
//   • fullscreen API request + re-prompt if user exits
// Returns: { strikes, events, requestFullscreen, fullscreen }
// ─────────────────────────────────────────────────────────────
function useIntegrity({ active, onStrike, onAutoSubmit, threshold = 5, allowSelection = false }) {
  const [strikes, setStrikes] = useStateI(0);
  const [events, setEvents] = useStateI([]);
  const [fullscreen, setFullscreen] = useStateI(false);
  const lastVis = useRefI(Date.now());

  const log = useCallbackI((kind, detail) => {
    const ev = { kind, detail, at: Date.now() };
    setEvents((es) => [...es.slice(-49), ev]);
    if (kind === 'visibility-hidden' || kind === 'paste-blocked' || kind === 'fullscreen-exit') {
      setStrikes((s) => {
        const ns = s + 1;
        onStrike?.(ns, ev);
        if (ns >= threshold) onAutoSubmit?.();
        return ns;
      });
    }
  }, [onStrike, onAutoSubmit, threshold]);

  useEffectI(() => {
    if (!active) return;

    const onVis = () => {
      if (document.hidden) {
        lastVis.current = Date.now();
        log('visibility-hidden', { ts: Date.now() });
      } else {
        const away = Math.round((Date.now() - lastVis.current) / 1000);
        log('visibility-return', { awaySeconds: away });
      }
    };
    const onBlur = () => log('window-blur', { ts: Date.now() });
    const onPaste = (e) => { e.preventDefault(); log('paste-blocked', {}); };
    const onCopy = (e) => { if (!allowSelection) { e.preventDefault(); log('copy-blocked', {}); } };
    const onContext = (e) => { e.preventDefault(); log('contextmenu-blocked', {}); };
    const onSel = (e) => { if (!allowSelection) e.preventDefault(); };
    const onFs = () => {
      const fs = !!(document.fullscreenElement || document.webkitFullscreenElement);
      setFullscreen(fs);
      if (!fs && active) log('fullscreen-exit', {});
    };

    document.addEventListener('visibilitychange', onVis);
    window.addEventListener('blur', onBlur);
    document.addEventListener('paste', onPaste, true);
    document.addEventListener('copy', onCopy, true);
    document.addEventListener('cut', onCopy, true);
    document.addEventListener('contextmenu', onContext);
    document.addEventListener('selectstart', onSel);
    document.addEventListener('fullscreenchange', onFs);
    document.addEventListener('webkitfullscreenchange', onFs);

    return () => {
      document.removeEventListener('visibilitychange', onVis);
      window.removeEventListener('blur', onBlur);
      document.removeEventListener('paste', onPaste, true);
      document.removeEventListener('copy', onCopy, true);
      document.removeEventListener('cut', onCopy, true);
      document.removeEventListener('contextmenu', onContext);
      document.removeEventListener('selectstart', onSel);
      document.removeEventListener('fullscreenchange', onFs);
      document.removeEventListener('webkitfullscreenchange', onFs);
    };
  }, [active, allowSelection, log]);

  const requestFullscreen = () => {
    const el = document.documentElement;
    try {
      if (el.requestFullscreen) el.requestFullscreen();
      else if (el.webkitRequestFullscreen) el.webkitRequestFullscreen();
    } catch {}
  };

  return { strikes, events, fullscreen, requestFullscreen };
}

// ─────────────────────────────────────────────────────────────
// IntegrityBanner — sticky banner showing strike count + warning
// ─────────────────────────────────────────────────────────────
function IntegrityBanner({ strikes, threshold = 5, fullscreen, onRequestFullscreen, onShowEvents }) {
  const remaining = threshold - strikes;
  const warn = strikes > 0;
  const danger = remaining <= 1;

  return (
    <div role="status" aria-live="polite" style={{
      position: 'sticky', top: 0, zIndex: 30,
      padding: '8px 14px',
      background: danger ? 'var(--danger-50)' : warn ? 'var(--warn-50)' : 'var(--p50)',
      borderBottom: `1px solid ${danger ? 'var(--danger-200)' : warn ? 'var(--warn-200)' : 'var(--p200)'}`,
      color: danger ? 'var(--danger-700)' : warn ? '#92400E' : 'var(--p700)',
      fontSize: 11, fontWeight: 600, display: 'flex', alignItems: 'center', gap: 10
    }}>
      <Icon name={danger ? 'alert' : warn ? 'eye' : 'shield'} size={14}/>
      <div style={{ flex: 1, minWidth: 0 }}>
        {strikes === 0 && <span>Proctored mode · don't switch apps · don't paste from outside</span>}
        {strikes > 0 && strikes < threshold && <span><strong>{strikes} of {threshold}</strong> integrity strikes used · {remaining} more before auto-submit</span>}
        {strikes >= threshold && <span><strong>Auto-submitted</strong> due to repeated app-switching</span>}
      </div>
      {!fullscreen && onRequestFullscreen && <button onClick={onRequestFullscreen} className="btn-sm" style={{ height: 26, padding: '0 8px', fontSize: 10.5, fontWeight: 700, background: '#fff', border: '1px solid currentColor', color: 'inherit', borderRadius: 7 }}>Fullscreen</button>}
      <button onClick={onShowEvents} className="btn-sm" style={{ height: 26, padding: '0 8px', fontSize: 10.5, fontWeight: 700, background: '#fff', border: '1px solid currentColor', color: 'inherit', borderRadius: 7 }}>Log</button>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// IntegrityEventsSheet — show what we logged (transparency builds trust)
// ─────────────────────────────────────────────────────────────
function IntegrityEventsSheet({ open, onClose, events }) {
  const labels = {
    'visibility-hidden': 'Switched away from test',
    'visibility-return': 'Returned to test',
    'window-blur': 'Browser lost focus',
    'paste-blocked': 'Paste prevented',
    'copy-blocked': 'Copy prevented',
    'contextmenu-blocked': 'Right-click prevented',
    'fullscreen-exit': 'Exited fullscreen'
  };
  return (
    <BottomSheet open={open} onClose={onClose} title="Integrity log">
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
        <div style={{ fontSize: 11.5, color: 'var(--n500)', lineHeight: 1.5, marginBottom: 6 }}>
          Everything logged below is shared with your teacher only if there's an investigation. We never log keystrokes or content.
        </div>
        {events.length === 0 && <EmptyState ic="check" tone="success" title="Clean record" sub="Nothing flagged so far. Keep going!"/>}
        {events.slice().reverse().map((ev, i) => (
          <div key={i} className="row" style={{ gap: 10, padding: '10px 0', borderBottom: '1px solid var(--n50)' }}>
            <div style={{ width: 32, height: 32, borderRadius: 9, background: ev.kind.includes('blocked') || ev.kind.includes('hidden') ? 'var(--warn-50)' : 'var(--n50)', color: ev.kind.includes('blocked') || ev.kind.includes('hidden') ? '#92400E' : 'var(--n500)', display: 'grid', placeItems: 'center' }}>
              <Icon name={ev.kind.includes('paste') ? 'copy' : ev.kind.includes('hidden') ? 'eye' : ev.kind.includes('return') ? 'check' : 'alert'} size={14}/>
            </div>
            <div style={{ flex: 1 }}>
              <div style={{ fontSize: 12, fontWeight: 700, color: 'var(--n900)' }}>{labels[ev.kind] || ev.kind}</div>
              <div style={{ fontSize: 10.5, color: 'var(--n500)', marginTop: 1 }}>{new Date(ev.at).toLocaleTimeString('en-IN', { hour: '2-digit', minute: '2-digit', second: '2-digit' })}{ev.detail?.awaySeconds ? ' · away ' + ev.detail.awaySeconds + 's' : ''}</div>
            </div>
          </div>
        ))}
      </div>
    </BottomSheet>
  );
}

// ─────────────────────────────────────────────────────────────
// useServerTimer — wraps a wall-clock timer with server-time reconciliation.
// In real usage you'd hit /api/time on every poll; we model the client → server
// drift correction so the test ends *when the server says it ends*, not when
// the device's wall clock says.
// ─────────────────────────────────────────────────────────────
function useServerTimer({ serverStartedAt, durationSec, active = true, onExpire }) {
  const [now, setNow] = useStateI(Date.now());
  const [drift, setDrift] = useStateI(0);
  const expired = useRefI(false);

  // Simulate periodic resync (in a real app, fetch /api/time)
  useEffectI(() => {
    if (!active) return;
    const sync = () => {
      // Modeled client/server drift: ±1.5s wobble
      const d = (Math.sin(Date.now() / 9000) * 1500) | 0;
      setDrift(d);
    };
    sync();
    const id = setInterval(sync, 30000);
    return () => clearInterval(id);
  }, [active]);

  useEffectI(() => {
    if (!active) return;
    const id = setInterval(() => setNow(Date.now()), 250);
    return () => clearInterval(id);
  }, [active]);

  const elapsed = Math.max(0, Math.floor((now - serverStartedAt + drift) / 1000));
  const remaining = Math.max(0, durationSec - elapsed);

  useEffectI(() => {
    if (active && remaining === 0 && !expired.current) {
      expired.current = true;
      onExpire?.();
    }
  }, [active, remaining, onExpire]);

  return { remaining, elapsed, drift };
}

// ─────────────────────────────────────────────────────────────
// TimerBar — visual countdown w/ resync ribbon
// ─────────────────────────────────────────────────────────────
function TimerBar({ remaining, total, drift }) {
  const pct = total > 0 ? Math.max(0, Math.min(100, (remaining / total) * 100)) : 0;
  const danger = remaining <= 60;
  const warn = remaining <= 300 && remaining > 60;
  const m = Math.floor(remaining / 60);
  const s = remaining % 60;

  return (
    <div style={{ padding: '10px 14px', background: danger ? 'var(--danger-50)' : warn ? 'var(--warn-50)' : '#fff', borderBottom: '1px solid var(--n100)' }}>
      <div className="spread" style={{ alignItems: 'center', marginBottom: 8 }}>
        <div className="row" style={{ gap: 8, alignItems: 'center' }}>
          <Icon name="history" size={14} color={danger ? 'var(--danger-600)' : warn ? '#92400E' : 'var(--n500)'}/>
          <span style={{ fontSize: 11, color: danger ? 'var(--danger-700)' : 'var(--n500)', textTransform: 'uppercase', letterSpacing: '.06em', fontWeight: 700 }}>
            {danger ? 'Final minute' : 'Time remaining'}
          </span>
        </div>
        <div style={{ fontSize: 22, fontWeight: 800, fontFamily: 'JetBrains Mono, monospace', letterSpacing: '-.02em', color: danger ? 'var(--danger-700)' : 'var(--n900)' }} aria-live="polite">
          {String(m).padStart(2, '0')}:{String(s).padStart(2, '0')}
        </div>
      </div>
      <div style={{ height: 4, borderRadius: 999, background: 'var(--n100)', overflow: 'hidden' }}>
        <div style={{
          height: '100%',
          width: pct + '%',
          background: danger ? 'linear-gradient(90deg, var(--danger-500), var(--danger-600))' : warn ? 'linear-gradient(90deg, #F59E0B, #D97706)' : 'linear-gradient(90deg, var(--p400), var(--p600))',
          transition: 'width 1s linear, background .3s'
        }}/>
      </div>
      <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 4, fontSize: 9.5, color: 'var(--n400)' }}>
        <span>Server-synced timer · drift {drift > 0 ? '+' : ''}{Math.round(drift / 100) / 10}s</span>
        <span>Auto-submits at 0:00</span>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// useAttemptDraft — autosaves answers per question to IndexedDB.
// Loads on mount; saves on every change (debounced).
// On reconnect after offline, the dirty saves are pushed up.
// ─────────────────────────────────────────────────────────────
function useAttemptDraft(attemptId) {
  const key = `attempt-${attemptId}`;
  const [answers, setAnswers] = useStateI({});
  const [savedAt, setSavedAt] = useStateI(null);
  const [hydrated, setHydrated] = useStateI(false);

  useEffectI(() => {
    let cancel = false;
    idbGet(key).then((v) => { if (cancel) return; if (v) setAnswers(v.answers || {}); setHydrated(true); });
    return () => { cancel = true; };
  }, [attemptId]);

  useEffectI(() => {
    if (!hydrated) return;
    const t = setTimeout(async () => {
      await idbSet(key, { answers, savedAt: Date.now() });
      setSavedAt(Date.now());
    }, 500);
    return () => clearTimeout(t);
  }, [answers, hydrated]);

  const setAnswer = (qid, val) => setAnswers((a) => ({ ...a, [qid]: val }));
  const clearDraft = async () => { await idbDel(key); setAnswers({}); setSavedAt(null); };

  return { answers, setAnswer, savedAt, hydrated, clearDraft };
}

// ─────────────────────────────────────────────────────────────
// AutosaveChip — small "saved 2s ago" indicator
// ─────────────────────────────────────────────────────────────
function AutosaveChip({ savedAt, online }) {
  const [tick, setTick] = useStateI(0);
  useEffectI(() => { const id = setInterval(() => setTick((t) => t + 1), 5000); return () => clearInterval(id); }, []);
  // tick ensures rerender for "ago" label
  void tick;

  const ago = savedAt ? Math.max(1, Math.round((Date.now() - savedAt) / 1000)) : null;
  const lab = !online ? 'Offline · saved locally'
    : ago == null ? 'Not yet saved'
    : ago < 5 ? 'Saved'
    : ago < 60 ? `Saved ${ago}s ago`
    : `Saved ${Math.round(ago / 60)}m ago`;

  return (
    <div className="row" style={{ gap: 6, fontSize: 10.5, color: !online ? '#92400E' : 'var(--n500)' }}>
      <span aria-hidden="true" style={{ width: 6, height: 6, borderRadius: '50%', background: !online ? '#F59E0B' : 'var(--success-500)' }}/>
      <span>{lab}</span>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// AssessmentRecoverySheet — shown on assessment intro when a draft exists
// ─────────────────────────────────────────────────────────────
function AssessmentRecoverySheet({ open, onClose, draft, onResume, onDiscard }) {
  if (!open || !draft) return null;
  const ago = Math.max(1, Math.round((Date.now() - (draft.savedAt || Date.now())) / 60000));
  const answered = Object.keys(draft.answers || {}).length;
  return (
    <BottomSheet open={open} onClose={onClose} title="Resume your test?">
      <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
        <div className="card" style={{ padding: 14, background: 'var(--p50)', borderColor: 'var(--p200)', display: 'flex', gap: 12 }}>
          <div style={{ width: 38, height: 38, borderRadius: 11, background: '#fff', color: 'var(--p600)', display: 'grid', placeItems: 'center' }}><Icon name="history" size={16}/></div>
          <div>
            <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--n900)' }}>Saved draft found</div>
            <div style={{ fontSize: 11.5, color: 'var(--n600)', marginTop: 2 }}>{answered} question{answered !== 1 ? 's' : ''} answered · {ago} min ago</div>
          </div>
        </div>
        <div style={{ fontSize: 11.5, color: 'var(--n500)', lineHeight: 1.5 }}>You disconnected before submitting. Your answers were saved locally. Pick where to go from here:</div>
        <div style={{ display: 'flex', gap: 10 }}>
          <button onClick={onDiscard} className="btn btn-outline" style={{ flex: 1 }}>Start over</button>
          <button onClick={onResume} className="btn" style={{ flex: 1.6 }}>Resume <Icon name="arrow-right" size={14}/></button>
        </div>
      </div>
    </BottomSheet>
  );
}

Object.assign(window, {
  useIntegrity, IntegrityBanner, IntegrityEventsSheet,
  useServerTimer, TimerBar,
  useAttemptDraft, AutosaveChip,
  AssessmentRecoverySheet
});
