// ─────────────────────────────────────────────────────────────
// primitives.jsx — production-hardening primitives
// Skeletons, Empty / Error / Offline states, BottomSheet, BiometricGate,
// PermissionPrompt, PullToRefresh, IndexedDB autosave helpers
// ─────────────────────────────────────────────────────────────
const { useState: useStateP, useEffect: useEffectP, useMemo: useMemoP, useRef: useRefP, useCallback: useCallbackP } = React;

// Skeletons --------------------------------------------------------------
function Skeleton({ w = '100%', h = 12, r = 6, style }) {
  return <div className="sk-shimmer" style={{ width: w, height: h, borderRadius: r, ...style }}/>;
}

function SkeletonCard({ lines = 3 }) {
  return (
    <div className="card" style={{ padding: 14 }}>
      <div className="row" style={{ gap: 12, alignItems: 'center' }}>
        <Skeleton w={36} h={36} r={11}/>
        <div style={{ flex: 1 }}>
          <Skeleton w="72%" h={12}/>
          <Skeleton w="42%" h={9} style={{ marginTop: 8 }}/>
        </div>
      </div>
      {Array.from({ length: Math.max(0, lines - 1) }).map((_, i) => (
        <Skeleton key={i} w={`${85 - i * 12}%`} h={9} style={{ marginTop: 10 }}/>
      ))}
    </div>
  );
}

function SkeletonList({ n = 3 }) {
  return <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>{Array.from({ length: n }).map((_, i) => <SkeletonCard key={i}/>)}</div>;
}

// Empty state ------------------------------------------------------------
function EmptyState({ ic = 'mail', title, sub, ctaLabel, onCta, tone = 'default' }) {
  const palette = tone === 'success'
    ? { bg: 'var(--success-50)', fg: '#065F46' }
    : tone === 'warn'
      ? { bg: 'var(--warn-50)', fg: '#92400E' }
      : { bg: 'var(--n50)', fg: 'var(--n400)' };
  return (
    <div role="status" style={{ padding: '36px 20px', textAlign: 'center', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 12 }}>
      <div style={{ width: 56, height: 56, borderRadius: 18, background: palette.bg, color: palette.fg, display: 'grid', placeItems: 'center' }}><Icon name={ic} size={26}/></div>
      <div>
        <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--n900)' }}>{title}</div>
        {sub && <div style={{ fontSize: 12, color: 'var(--n500)', marginTop: 6, lineHeight: 1.5, maxWidth: 280 }}>{sub}</div>}
      </div>
      {ctaLabel && <button className="btn-sm btn-soft" onClick={onCta} style={{ marginTop: 4 }}>{ctaLabel}</button>}
    </div>
  );
}

// Error / Retry banner ---------------------------------------------------
function ErrorBanner({ title = "Couldn't load", sub, onRetry }) {
  return (
    <div className="card" role="alert" style={{ padding: 14, borderColor: 'var(--danger-200)', background: 'var(--danger-50)', display: 'flex', alignItems: 'center', gap: 12 }}>
      <div style={{ width: 36, height: 36, borderRadius: 11, background: '#fff', color: 'var(--danger-600)', display: 'grid', placeItems: 'center', flexShrink: 0 }}><Icon name="alert" size={18}/></div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--n900)' }}>{title}</div>
        {sub && <div style={{ fontSize: 11, color: 'var(--n600)', marginTop: 2 }}>{sub}</div>}
      </div>
      {onRetry && <button onClick={onRetry} className="btn-sm" style={{ background: '#fff', border: '1px solid var(--danger-200)', color: 'var(--danger-700)', height: 32, padding: '0 12px', borderRadius: 10, fontSize: 12, fontWeight: 700 }}>Retry</button>}
    </div>
  );
}

// Online / Offline -------------------------------------------------------
function useOnline() {
  const [online, setOnline] = useStateP(typeof navigator !== 'undefined' ? navigator.onLine !== false : true);
  useEffectP(() => {
    const on = () => setOnline(true);
    const off = () => setOnline(false);
    window.addEventListener('online', on);
    window.addEventListener('offline', off);
    return () => { window.removeEventListener('online', on); window.removeEventListener('offline', off); };
  }, []);
  return online;
}

function OfflineBanner({ message = "You're offline — work will sync when back online" }) {
  const online = useOnline();
  if (online) return null;
  return (
    <div role="status" aria-live="polite" style={{
      position: 'sticky', top: 0, zIndex: 50,
      background: 'var(--n900)', color: '#fff',
      padding: '8px 14px', fontSize: 11, fontWeight: 600,
      display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8
    }}>
      <span aria-hidden="true" style={{ width: 6, height: 6, borderRadius: '50%', background: '#F59E0B', boxShadow: '0 0 0 4px rgba(245,158,11,.25)' }}/>
      {message}
    </div>
  );
}

// PullToRefresh ----------------------------------------------------------
function PullToRefresh({ onRefresh, children }) {
  const [pull, setPull] = useStateP(0);
  const [refreshing, setRefreshing] = useStateP(false);
  const startY = useRefP(null);
  const wrap = useRefP(null);

  const onStart = (e) => {
    if (wrap.current && wrap.current.scrollTop > 0) return;
    startY.current = e.touches ? e.touches[0].clientY : null;
  };
  const onMove = (e) => {
    if (startY.current == null || refreshing) return;
    const y = e.touches[0].clientY;
    const d = Math.max(0, Math.min(120, y - startY.current));
    if (d > 0) setPull(d);
  };
  const onEnd = async () => {
    if (pull > 70 && !refreshing) {
      setRefreshing(true);
      setPull(60);
      try { await onRefresh?.(); } finally { setRefreshing(false); }
    }
    setPull(0);
    startY.current = null;
  };

  return (
    <div ref={wrap} onTouchStart={onStart} onTouchMove={onMove} onTouchEnd={onEnd} style={{ overflowY: 'auto', height: '100%', WebkitOverflowScrolling: 'touch' }}>
      <div style={{
        height: pull, transition: pull === 0 ? 'height .2s' : 'none',
        display: 'grid', placeItems: 'center', overflow: 'hidden', color: 'var(--n400)'
      }}>
        <div style={{ animation: refreshing ? 'sp-spin 1s linear infinite' : 'none', transform: refreshing ? 'none' : `rotate(${pull * 3}deg)` }}>
          <Icon name="history" size={18}/>
        </div>
      </div>
      {children}
    </div>
  );
}

// BottomSheet — generic --------------------------------------------------
function BottomSheet({ open, onClose, title, children, footer, maxHeight = '88vh' }) {
  if (!open) return null;
  return (
    <>
      <div onClick={onClose} aria-hidden="true" style={{
        position: 'absolute', inset: 0, background: 'rgba(15,23,42,.45)', zIndex: 80,
        animation: 'sp-fade .2s ease-out'
      }}/>
      <div role="dialog" aria-modal="true" aria-label={title || 'Sheet'} style={{
        position: 'absolute', left: 0, right: 0, bottom: 0,
        background: '#fff', borderRadius: '20px 20px 0 0',
        zIndex: 81, maxHeight, display: 'flex', flexDirection: 'column',
        animation: 'sp-up .25s cubic-bezier(.2,.9,.3,1)',
        boxShadow: '0 -20px 50px rgba(15,23,42,.18)'
      }}>
        <div className="sheet-handle"/>
        {title && (
          <div className="spread" style={{ padding: '4px 20px 8px' }}>
            <div style={{ fontSize: 17, fontWeight: 800, color: 'var(--n900)' }}>{title}</div>
            <button onClick={onClose} aria-label="Close" style={{ width: 32, height: 32, borderRadius: 10, background: 'var(--n50)', display: 'grid', placeItems: 'center' }}><Icon name="x" size={16}/></button>
          </div>
        )}
        <div style={{ overflowY: 'auto', flex: 1, padding: '8px 20px 16px' }}>{children}</div>
        {footer && <div style={{ padding: '12px 20px 28px', borderTop: '1px solid var(--n100)' }}>{footer}</div>}
      </div>
    </>
  );
}

// BiometricGate — Face ID / PIN modal ------------------------------------
function BiometricGate({ open, onAuthorize, onCancel, title = 'Confirm with Face ID', sub = "High-value action — verify it's you", amount, mode = 'face' }) {
  const [stage, setStage] = useStateP('idle'); // idle | scanning | ok | failed
  const [pin, setPin] = useStateP('');
  const [usePin, setUsePin] = useStateP(false);

  useEffectP(() => { if (open) { setStage('idle'); setPin(''); setUsePin(mode === 'pin'); } }, [open, mode]);

  const start = () => {
    setStage('scanning');
    setTimeout(() => {
      setStage('ok');
      setTimeout(() => onAuthorize?.(), 480);
    }, 900);
  };

  const confirmPin = () => {
    if (pin.length < 4) return;
    setStage('scanning');
    setTimeout(() => { setStage('ok'); setTimeout(() => onAuthorize?.(), 480); }, 500);
  };

  if (!open) return null;
  const ok = stage === 'ok';
  return (
    <>
      <div onClick={stage === 'idle' ? onCancel : undefined} aria-hidden="true" style={{ position: 'absolute', inset: 0, background: 'rgba(15,23,42,.55)', backdropFilter: 'blur(6px)', zIndex: 90 }}/>
      <div role="dialog" aria-modal="true" aria-label={title} style={{
        position: 'absolute', left: 16, right: 16, top: '20%',
        background: '#fff', borderRadius: 24, padding: '24px 22px 20px',
        zIndex: 91, textAlign: 'center', display: 'flex', flexDirection: 'column', gap: 14, alignItems: 'center',
        animation: 'sp-up .25s cubic-bezier(.2,.9,.3,1)',
        boxShadow: '0 30px 60px rgba(15,23,42,.32)'
      }}>
        <div aria-hidden="true" style={{
          width: 84, height: 84, borderRadius: '50%',
          background: ok ? 'var(--success-50)' : 'var(--p50)',
          display: 'grid', placeItems: 'center', position: 'relative',
          transition: 'background .3s'
        }}>
          {stage === 'scanning' && (
            <div style={{
              position: 'absolute', inset: -4, borderRadius: '50%',
              border: '3px solid var(--p400)', borderTopColor: 'transparent',
              animation: 'sp-spin 1s linear infinite'
            }}/>
          )}
          <Icon name={ok ? 'check' : (usePin ? 'lock' : 'shield')} size={38} color={ok ? '#065F46' : 'var(--p600)'}/>
        </div>
        <div>
          <div style={{ fontSize: 17, fontWeight: 800, color: 'var(--n900)' }}>{ok ? 'Verified' : (usePin ? 'Enter your PIN' : title)}</div>
          <div style={{ fontSize: 12, color: 'var(--n500)', marginTop: 4, maxWidth: 260 }}>{ok ? 'Continuing securely' : sub}</div>
          {!ok && amount && <div style={{ fontSize: 22, fontWeight: 800, color: 'var(--n900)', marginTop: 10, fontFamily: 'JetBrains Mono, monospace', letterSpacing: '-.02em' }}>₹{Number(amount).toLocaleString('en-IN')}</div>}
        </div>

        {!ok && usePin && (
          <div style={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 10 }}>
            <div style={{ display: 'flex', justifyContent: 'center', gap: 10 }}>
              {[0, 1, 2, 3].map((i) => (
                <div key={i} style={{ width: 14, height: 14, borderRadius: '50%', border: '2px solid var(--n200)', background: i < pin.length ? 'var(--p500)' : 'transparent', borderColor: i < pin.length ? 'var(--p500)' : 'var(--n200)' }}/>
              ))}
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8 }}>
              {['1', '2', '3', '4', '5', '6', '7', '8', '9', '', '0', '⌫'].map((k, i) => (
                <button key={i} disabled={!k} aria-label={k === '⌫' ? 'Delete' : k}
                  onClick={() => {
                    if (k === '⌫') setPin((p) => p.slice(0, -1));
                    else if (k && pin.length < 4) setPin((p) => p + k);
                  }}
                  style={{ height: 44, borderRadius: 12, background: k ? 'var(--n50)' : 'transparent', fontSize: 18, fontWeight: 700, color: 'var(--n900)', visibility: k ? 'visible' : 'hidden', border: 'none' }}>{k}</button>
              ))}
            </div>
          </div>
        )}

        {stage === 'idle' && !usePin && (
          <div style={{ display: 'flex', gap: 8, width: '100%', marginTop: 4 }}>
            <button className="btn btn-outline" onClick={onCancel} style={{ flex: 1 }}>Cancel</button>
            <button className="btn" onClick={start} style={{ flex: 1.4 }}>Verify</button>
          </div>
        )}
        {stage === 'idle' && usePin && (
          <button className="btn" onClick={confirmPin} disabled={pin.length < 4} style={{ width: '100%', opacity: pin.length < 4 ? .5 : 1 }}>Continue</button>
        )}
        {stage === 'idle' && (
          <button onClick={() => setUsePin((p) => !p)} className="btn-ghost" style={{ fontSize: 11, color: 'var(--n400)' }}>{usePin ? 'Use Face ID instead' : 'Use PIN instead'}</button>
        )}

        <div style={{ fontSize: 10.5, color: 'var(--n400)', display: 'flex', alignItems: 'center', gap: 6 }}>
          <Icon name="lock" size={11}/> Secured · 256-bit encryption
        </div>
      </div>
    </>
  );
}

// PermissionPrompt — soft pre-ask before native permission ----------------
function PermissionPrompt({ open, kind = 'notifications', onAllow, onSkip }) {
  const meta = {
    notifications: { ic: 'bell', t: 'Stay on top of school updates', s: "We'll send a push when fees fall due, results publish, or your child's bus is 3 min away. Mute anytime." },
    camera: { ic: 'camera', t: 'Snap your homework', s: 'Use the camera to capture pages. Photos stay on your device until you submit.' },
    location: { ic: 'pin', t: 'See the bus on the map', s: 'We use location only when Transport is open. Never tracked in the background.' },
    sms: { ic: 'message', t: "We'll fall back to SMS", s: "If push isn't available on this device, critical updates (fees, attendance, results) come as SMS. Standard carrier rates apply." }
  }[kind] || { ic: 'bell', t: 'Allow access?', s: '' };
  return (
    <BottomSheet open={open} onClose={onSkip}>
      <div style={{ textAlign: 'center', padding: '8px 4px 16px', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 12 }}>
        <div style={{ width: 64, height: 64, borderRadius: 20, background: 'var(--p50)', color: 'var(--p600)', display: 'grid', placeItems: 'center' }}><Icon name={meta.ic} size={28}/></div>
        <div>
          <div style={{ fontSize: 18, fontWeight: 800, color: 'var(--n900)' }}>{meta.t}</div>
          <div style={{ fontSize: 12, color: 'var(--n500)', marginTop: 6, lineHeight: 1.5, maxWidth: 280 }}>{meta.s}</div>
        </div>
        <div style={{ fontSize: 10.5, color: 'var(--n400)', display: 'flex', alignItems: 'center', gap: 6 }}>
          <Icon name="lock" size={11}/> You can change this in Settings → Notifications
        </div>
        <div style={{ display: 'flex', gap: 8, width: '100%', marginTop: 8 }}>
          <button onClick={onSkip} className="btn btn-outline" style={{ flex: 1 }}>Not now</button>
          <button onClick={() => {
            try {
              if (kind === 'notifications' && typeof Notification !== 'undefined' && Notification.permission === 'default') {
                Notification.requestPermission();
              }
            } catch (e) { /* sandboxed */ }
            onAllow?.();
          }} className="btn" style={{ flex: 1.4 }}>Allow</button>
        </div>
      </div>
    </BottomSheet>
  );
}

// IndexedDB helpers ------------------------------------------------------
function idbOpen() {
  return new Promise((res, rej) => {
    if (typeof indexedDB === 'undefined') return rej(new Error('No IndexedDB'));
    const r = indexedDB.open('scholiq', 1);
    r.onupgradeneeded = () => { try { r.result.createObjectStore('kv'); } catch {} };
    r.onsuccess = () => res(r.result);
    r.onerror = () => rej(r.error);
  });
}
async function idbSet(key, val) {
  try {
    const db = await idbOpen();
    return new Promise((res, rej) => {
      const tx = db.transaction('kv', 'readwrite');
      tx.objectStore('kv').put(val, key);
      tx.oncomplete = () => res(true);
      tx.onerror = () => rej(tx.error);
    });
  } catch {
    try { localStorage.setItem('sk:' + key, JSON.stringify(val)); } catch {}
    return false;
  }
}
async function idbGet(key) {
  try {
    const db = await idbOpen();
    return new Promise((res, rej) => {
      const tx = db.transaction('kv', 'readonly');
      const r = tx.objectStore('kv').get(key);
      r.onsuccess = () => res(r.result);
      r.onerror = () => rej(r.error);
    });
  } catch {
    try { const v = localStorage.getItem('sk:' + key); return v ? JSON.parse(v) : undefined; } catch { return undefined; }
  }
}
async function idbDel(key) {
  try {
    const db = await idbOpen();
    return new Promise((res) => {
      const tx = db.transaction('kv', 'readwrite');
      tx.objectStore('kv').delete(key);
      tx.oncomplete = () => res(true);
    });
  } catch { try { localStorage.removeItem('sk:' + key); } catch {} return false; }
}

function useAutosave(key, value, delay = 700) {
  useEffectP(() => {
    if (!key) return;
    const t = setTimeout(() => { idbSet(key, value); }, delay);
    return () => clearTimeout(t);
  }, [key, JSON.stringify(value), delay]);
}

// useDraft — load → mutable state → autosave
function useDraft(key, initial) {
  const [value, setValue] = useStateP(initial);
  const [hydrated, setHydrated] = useStateP(false);
  useEffectP(() => {
    let cancel = false;
    if (!key) { setHydrated(true); return; }
    idbGet(key).then((v) => { if (cancel) return; if (v) setValue((cur) => ({ ...cur, ...v })); setHydrated(true); });
    return () => { cancel = true; };
  }, [key]);
  useAutosave(key, hydrated ? value : null);
  return [value, setValue, hydrated];
}

// PageHeader — re-usable top-bar with safe-area + back ------------------
function PageHeader({ title, subtitle, onBack, right, sticky = true, tone = 'light' }) {
  const dark = tone === 'dark';
  return (
    <div style={{
      position: sticky ? 'sticky' : 'static', top: 0, zIndex: 5,
      background: dark ? 'var(--n900)' : '#fff',
      borderBottom: dark ? 'none' : '1px solid var(--n100)',
      padding: '52px 16px 12px',
      color: dark ? '#fff' : 'var(--n900)'
    }}>
      <div className="spread" style={{ alignItems: 'center', gap: 8 }}>
        <button onClick={onBack} aria-label="Back" style={{ width: 36, height: 36, borderRadius: 11, background: dark ? 'rgba(255,255,255,.12)' : 'var(--n50)', display: 'grid', placeItems: 'center', color: dark ? '#fff' : 'var(--n900)' }}>
          <Icon name="chevron-left" size={18}/>
        </button>
        <div style={{ flex: 1, minWidth: 0, textAlign: 'center' }}>
          <div style={{ fontSize: 15, fontWeight: 800, lineHeight: 1.2, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{title}</div>
          {subtitle && <div style={{ fontSize: 11, opacity: .65, marginTop: 2 }}>{subtitle}</div>}
        </div>
        <div style={{ display: 'flex', gap: 6 }}>{right || <span style={{ width: 36 }}/>}</div>
      </div>
    </div>
  );
}

// SegmentedControl -----------------------------------------------------
function Segmented({ value, onChange, options }) {
  return (
    <div role="tablist" style={{ display: 'flex', background: 'var(--n50)', borderRadius: 12, padding: 4, gap: 4 }}>
      {options.map((o) => {
        const v = typeof o === 'string' ? o : o.value;
        const lab = typeof o === 'string' ? o : o.label;
        const active = v === value;
        return (
          <button key={v} role="tab" aria-selected={active} onClick={() => onChange(v)} style={{
            flex: 1, height: 32, borderRadius: 9, border: 'none',
            background: active ? '#fff' : 'transparent', color: active ? 'var(--n900)' : 'var(--n500)',
            fontSize: 12, fontWeight: 700, boxShadow: active ? '0 1px 3px rgba(15,23,42,.08)' : 'none',
            transition: 'all .18s'
          }}>{lab}</button>
        );
      })}
    </div>
  );
}

// FormField ------------------------------------------------------------
function FormField({ label, hint, error, children, required }) {
  return (
    <label style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
      <div style={{ fontSize: 11, fontWeight: 700, color: 'var(--n600)', display: 'flex', justifyContent: 'space-between' }}>
        <span>{label}{required && <span style={{ color: 'var(--c500)', marginLeft: 2 }}>*</span>}</span>
        {hint && <span style={{ fontWeight: 500, color: 'var(--n400)' }}>{hint}</span>}
      </div>
      {children}
      {error && <div style={{ fontSize: 11, color: 'var(--danger-600)', display: 'flex', alignItems: 'center', gap: 4 }}><Icon name="alert" size={11}/> {error}</div>}
    </label>
  );
}

// Inline animations + skeleton shimmer (registered once) ----------------
(() => {
  if (document.getElementById('sp-anim-prims')) return;
  const s = document.createElement('style');
  s.id = 'sp-anim-prims';
  s.textContent = `
    @keyframes sp-fade { from { opacity: 0 } to { opacity: 1 } }
    @keyframes sp-up { from { transform: translateY(20px); opacity: 0 } to { transform: translateY(0); opacity: 1 } }
    @keyframes sp-spin { to { transform: rotate(360deg) } }
    @keyframes sp-shimmer { 0% { background-position: 200% 0 } 100% { background-position: -200% 0 } }
    .sk-shimmer { background: linear-gradient(90deg, var(--n100) 0%, var(--n50) 50%, var(--n100) 100%); background-size: 200% 100%; animation: sp-shimmer 1.4s ease-in-out infinite; }
    @media (prefers-reduced-motion: reduce) {
      .sk-shimmer { animation: none; background: var(--n100); }
      [class*="sp-"] { animation: none !important; }
    }
    .focus-ring:focus-visible { outline: 2px solid var(--p500); outline-offset: 2px; border-radius: 8px; }
  `;
  document.head.appendChild(s);
})();

Object.assign(window, {
  Skeleton, SkeletonCard, SkeletonList,
  EmptyState, ErrorBanner, OfflineBanner, useOnline,
  PullToRefresh, BottomSheet,
  BiometricGate, PermissionPrompt,
  idbSet, idbGet, idbDel, useAutosave, useDraft,
  PageHeader, Segmented, FormField
});
